Home > Blockchain >  Typescript indexing a partial type, react-hook-form dirty fields to updated fields
Typescript indexing a partial type, react-hook-form dirty fields to updated fields

Time:12-13

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.

Playground link to code

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
}
  • Related