'React + TS. Cannot use union keys as index type
I got a hook with useState and delete method to manage my form values.
const [values, setValues] = useState<tAllValues>({});
My values would be:
{
name: 'Andrew',
age: 34,
avatar: [{
name: 'avatar.png',
size: 45234,
...
}]
}
These values can be from all forms so have tAllValues type which is union of all form types.
type tForm1 = { name: string; age: number; };
type tForm2 = { avatar: File };
type tAllValues = tForm1 | tForm2;
I need to have a delete method to delete file. File is normal object in values and here is my method:
const deleteFile = (fileId: string, fieldName: KeysOfUnion<tAllValues>) => {
const fieldValue = values[fieldName];
const filteredFiles = fieldValue.filter((value) => value.id !== fileId);
setValues({ ...values, [fieldName]: filteredFiles });
};
fieldName have type KeysOfUnion because it need to be one of keys from tAllValues and here is a problem.
export type KeysOfUnion<T> = T extends any ? keyof T : never;
I can see that fieldName is 'name' | 'age' | 'avatar' but I got a TypeScript error:
Element implicitly has an 'any' type because expression of type '"name" | "age" | "avatar"' can't be used to index type 'tAllValues'.Property 'name' does not exist on type 'tAllValues'.ts(7053)
This happens probably because there is no 'name' in tForm2 type but it is in intForm1 type.
Is the way TS help to fix it?
Solution 1:[1]
Rather than accept all keys of the union, instead accept only the keys of the object you know you are manipulating. So purify the function by passing in values as a parameter, then use a generic to obtain the type of v.
const deleteFile = <T extends tAllValues>(v: T, fileId: string, fieldName: keyof T) => {
const fieldValue = v[fieldName];
const filteredFiles = fieldValue.filter((value) => value.id !== fileId);
//^ ERROR: Property 'filter' does not exist on type 'T[keyof T]'
};
However this still has an error, since you could use any fieldValue and return a value that is not an array. So you get another error. You can narrow this by checking if it is an array.
if (Array.isArray(fieldValue)) {
const filteredFiles = fieldValue.filter((value) => value.id !== fileId);
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Cody Duong |
