In react-hook-form
you get dirtyFields
which are the updated fields = true
v = {
username: 'alice',
email: '[email protected]',
role: 'USER'
}
dirtyFields = {
username: true
}
I want to only send the updated fields (username in this case)
I tried this
type User = {
username: string
email: string
role: 'ADMIN' | 'USER'
}
function submitForm (v: User, dirtyFields: Partial<Record<keyof User, boolean>>) {
const updated: Partial<User> = {}
Object.keys(dirtyFields).forEach(field => {
updated[field] = v[field as keyof User]
})
return updated
}
But I'm getting this error in updated[field]
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Partial<User>'.
No index signature with a parameter of type 'string' was found on type 'Partial<User>'.(7053)
CodePudding user response:
The error is that field
is seen as type string
, and User
doesn't have a string index signature. This is a consequence of the fact that Object.keys(obj)
returns string[]
instead of Array<keyof typeof obj>
, for reasons laid out in the answer to Why doesn't Object.keys return a keyof type in TypeScript?.
So if you want the compiler to treat each element at keyof User
, you'll need to assert the type somewhere. My choice is usually to write (Object.keys(dirtyFields) as Array<keyof User>)
.
But you'd still have an error:
(Object.keys(dirtyFields) as Array<keyof User>).
forEach(field => {
updated[field] = v[field]; // error!
//^^^^^^^^^^^^
// Type 'string' is not assignable to type '"ADMIN" | "USER" | undefined'.
});
because the compiler doesn't follow the logic that field
is identical on both sides of the assignment. It is looking at the types and not variable identities, so it treats that assignment like updated[field1] = v[field2]
where field1
and field2
are both of type keyof User
. It makes sense that this should be an error, but precisely because field1
and field2
might be different. This error was implemented in microsoft/TypeScript#30769 but has caused a bunch of grief for TS programmers. The workaround/fix for this is laid out in a comment on the same issue: use generics:
(Object.keys(dirtyFields) as Array<keyof User>).
forEach(<K extends keyof User>(field: K) => {
updated[field] = v[field]; // okay
})
Now the callback to forEach()
is generic in K
, the type of field
, which is constrained to keyof User
. And the compiler sees the right side of the assignment as User[K]
while the left is Partial<User>[K]
, and the assignment succeeds.
CodePudding user response:
User word#1438
on Discord gave me a solution
type User = {
username: string
email: string
role: 'ADMIN' | 'USER'
}
function sendValues(u: User, dirtyValues: Partial<Record<keyof User, boolean>>) {
const updated: Partial<Record<keyof User, string>> = {}
function isTypeOfUser(key: string): key is keyof User {
return key in u
}
Object.keys(dirtyValues).forEach(f => {
if(!isTypeOfUser(f)) return
updated[f] = u[f as keyof User]
})
return updated
}