Problem
I have two reducers that use the exact same logic for two different arrays containing points. An array will only have points of a single type; these types are A and B. I want to create a reducer that accepts an array of points of type A or type B and then returns an array of that same type.
When I try to compile the code below, I get this error:
Argument of type 'Partial<A> | Partial<B>' is not assignable to parameter of type 'Partial<T>'.
Type 'Partial<A>' is not assignable to type 'Partial<T>'.
How can I fix this?
Problematic Code
enum Category {
A,
B
}
interface A {
readonly category: Category.A;
}
interface B {
readonly category: Category.B;
}
Category =
const genericReducer = <T extends A | B>(
state: MyState,
action: Actions,
points: T[],
category: Category
): T[] => {
switch (action.type) {
case POINT_UPDATED: {
if (action.payload.category !== category) return points;
return updateItemInArray(points, action.payload.id, (stpt) => {
return updateObject(stpt, action.payload.newValues);
});
}
default:
return points;
}
};
ArrayUtils.updateObject
For reference, here is the updateObject function:
static updateObject = <T>(oldObject: T, newValues: Partial<T>) => {
// Encapsulate the idea of passing a new object as the first parameter
// to Object.assign to ensure we correctly copy data instead of mutating
return Object.assign({}, oldObject, newValues);
};
updateItemInArray Function
static updateItemInArray = <T>(array: T[], itemId: string, updateItemCallback: (item: T) => T): T[] => {
const updatedItems = array.map((item) => {
if (item.id !== itemId) {
// Since we only want to update one item, preserve all others as they are now
return item;
}
// Use the provided callback to create an updated item
const updatedItem = updateItemCallback(item);
return updatedItem;
});
return updatedItems;
};
EDIT #1
Here is a CodeSandbox link to my latest attempt based on Linda Paiste's answer. At this time there is still an issue assigning to type Partial<T>.
I started trying to fill in the missing pieces of your code in order to find where the problem is. Doing that, it became apparent that the issue is in your
Actiontype (as suggested by @Alex Chashin).I know that
Actionshas atypewhich needs to includePOINT_UPDATED. It also has apayload. The payload includes anid, acategory, and somenewValues.What is
newValues? Based on the signature ofupdateObject, we know that it should bePartial<T>. ButActionsdoesn't know whatTis.I'm guessing that your code uses something like
newValues: A | B;, which gives me the error that you posted. This is not specific enough. It's not enough to know that our new values are "AorB".updateObjectsays that ifTisAthennewValuesmust beAand ifTisBthen newValues must beB.Therefore your
Actionsneeds to be a generic type.I'm seeing an error on your
updateItemInArraywhen trying to accessitem.id:You need to refine the type of
Tsuch it knows about the id property:Doing this causes new errors in your
genericReducerbecause you have said thatTmust have acategorybut you haven't said that it must have anid. We can fix that with:which is the same as
Though it actually appears that you are never looking at the
categoryon your pointsT[]. So all you really need is:TypeScript Playground Link
Edit:
The error that you are getting in your revised code is honestly really dumb. Technically
Textendsa type with{elevation: number}. So there is the potential that it could require a more specific version of the type, like{elevation: 99}. In that particular case,{elevation: number}would not be assignable toPartial<{elevation: 99}>.At least I thought that was the problem. However my first fix did not work. I tried to refine the type of
Actionsto say that theelevationproperty must match the one fromT.But I'm still getting an error, so now I'm thoroughly confused.
I really can't explain that one.
You might have to make an assertion instead. (Don't bother with the fix above).
We avoid errors within the
updateObjectfunction by setting the genericTof that function to{elevation: number}. Bothstpt(typeT) and{elevation: number}are assignable to that type.But now the return type of the
updateObjectfunction is{elevation: number}instead ofT. So we need to assertas Tin order to change the type.