I ask this mostly because I'm curious. The title is maybe a little complicated so... Let's say I have an object like this:
type ObjType = { items: Array<{ id: number }>; sth: number };
const obj: ObjType = {
sth: 3,
items: [{ id: 1 }, { id: 2 }, { id: 3 }]
};
Now I would want to remove an item from obj.items
by id, let's say 2 and return the whole object with that item removed (new object, not mutated). This is generally straightforward in JavaScript, but I would want to do it with type safety preserved in TypeScript. I came up with something like this:
function removeItemFromArray<S, A extends { id: number }>(
obj: S,
field: keyof S,
array: A[],
valueToCompare: number
): S {
return {
...obj,
[field]: array.filter(i => i.id !== valueToCompare)
};
}
but the array
param seems redundant - it will always be obj[field]
. But if I just used obj[field]
there's no guarantee that it's an array. Now the question is how to make sure that obj[field]
is an array of objects that contain id property? I'm pretty sure it's possible with conditional types but I can't quite figure it out.
Conditional types won't help here, but you can get what you want by ensuring that obj
has the right shape:
function removeItemFromArray<S extends { [f in F]: { id: number }[] }, F extends keyof S>(
obj: S,
field: F,
valueToCompare: number
): S {
return {
...obj,
[field]: obj[field].filter(i => i.id !== valueToCompare)
};
}
const obj = {
sth: 3,
items: [{ id: 1 }, { id: 2 }, { id: 3 }]
};
removeItemFromArray(obj, "items", 2);