Search code examples
javascriptreactjstypescripttypesoptional-chaining

How to set a good condition for dynamic property of object


I have an array of objects:

export const inputsArray: InputAttributes[] = [
    {
        label: 'Name',
        type: 'text',
        name: 'name',
        required: true
    },
    {
        label: 'User name',
        type: 'text',
        name: 'username',
        required: true
    },
    ...
]

and map it like so:

const inputs = inputsArray.map(input => {
    const value = (input.name === 'street' || input.name === 'city' || input.name === 'zipcode')  // <=  PROBLEM : NOT EFFECTIVE CONDITION!
        ? user?.address?.[input.name] 
        : user?.[input.name]   // <= TYPESCRIPT PROBLEM 

    return (
        <Input
            key={input.name}
            defaultValue={value} 
            input={input}
            disabled={disabled}
            onChange={hadndleChange}
        />
    )
})

Info in user:

export interface User {
    id: number,
    name: string,
    username: string,
    email: string,
    address: {
        street: string,
        city: string,
        zipcode: string,
    }
    phone: string,
    website: string,
    company: {
        name: string
    }
}
  1. As you can see, inside map method I have a condition for value. But it's not good, because if user object have more additional properties inside address or company or other property with object as value, I need to put more conditions inside. Is there any better pattern for doing that?
  2. user?.[input.name] Typescrips says that "type 'string' can't be used to index type 'Users'". I can avoid that problem by setting [key: string] : any inside User interface, but is there any better pattern?

Input attributes interface if needed:

export interface InputAttributes {
    label: string,
    type: string,
    name: string,
    pattern?: string,
    required: boolean
}

Solution

  • You should modify the InputAttributes interface like this:

    type DeepKeyOf<T> = {
      [K in keyof T]: T[K] extends Record<string, any> ? DeepKeyOf<T[K]> : K
    }[keyof T]
    
    export interface InputAttributes {
        label: string,
        type: string,
        name: DeepKeyOf<User>,
        pattern?: string,
        required: boolean
    }
    

    Now TypeScript knows that the name property must contain some key of User.