I'm using useReducer to maintain state for an array of object types and I have a few action types. The interfaces looks like:
interface MyObject {
id: string;
foo: string;
bar: string;
}
interface Action {
type: 'add' | 'remove' | 'update';
payload?: Partial<MyObject>;
}
The reducer function:
const reducer = (current: MyObject[], action: Action): MyObject[] => {
switch (action.type) {
case 'add':
return [...current, createBaseMyObject()];
case 'remove':
return current.filter((item) => item.id !== action.payload?.id);
case 'update':
return current.map((item) => {
if (item.id === action.payload?.id) {
return { ...item, ...action.payload };
}
return item;
});
default:
return current;
}
};
That works ok but I also need to conditionally pass an array of MyObject as the payload to update in bulk. Tried changing types and function to this:
interface ReducerAction {
type: 'add' | 'remove' | 'update';
payload?: Partial<MyObject> | MyObject[];
}
const reducer = (current: MyObject[], action: Action): MyObject[] => {
switch (action.type) {
case 'add':
return [...current, createBaseMyObject()];
case 'remove':
// Property 'id' does not exist on type 'MyObject[]'
return current.filter((item) => item.id !== action.payload?.id);
case 'update':
// With proper typing maybe this should be dedicated case
if (Array.isArray(action.payload)) {
return action.payload;
}
return current.map((item) => {
// Property 'id' does not exist on type 'MyObject[]'
if (item.id === action.payload?.id) {
return { ...item, ...action.payload };
}
return item;
});
default:
return current;
}
};
I'm trying to avoid assertion or extra runtime checks just to satisfy TS. If creating a dedicated case makes it easier that's fine. I'd just want to type it best as possible and ideally keep it simple. Thoughts?