Search code examples
typescriptreact-typescript

Property x does not exist on type 'object'


I have an interface like this:

export interface IData {
  mappings: object
}

Mappings object is like this:

    mappings = {
    "car": ["benz","toyota","ford"],
    "bike" : ["enfield", "pulsar"]
    }

Now, I have a formik form in my code, and I am calling a function getOptions(value) in the onChange method of one of my form fields. The getOptions function is like this:

    const getOptions = (value: string | number | boolean | (string | number | boolean)[] | undefined) => {
            console.log("value", value)
            console.log(contextOptionMappings.car) //error here
           console.log(contextOptionMappings.value) //error here
        }

What I am actually trying to do is that: if value is "car" then I would like to print

 ["benz","toyota","ford"]

But, when I try to print contextOptionMappings.car as car key already exists in mappings object, I am getting error as Property 'car' does not exist on type 'object'.

I also tried

console.log(contextOptionMappings[value])

But this gives the error

Type '(string | number | boolean)[]' cannot be used as an index type.ts(2538)
Type 'false' cannot be used as an index type.ts(2538)
Type 'true' cannot be used as an index type.ts(2538)
Type 'undefined' cannot be used as an index type

If I change the type to any, like this:

const getOptions = (value: any) 

Element implicitly has an 'any' type because expression of type 'any' can't be used to index type '{}'

How do I get the values ?


Solution

  • The error states that you cannot use the type '(string | number | boolean)[]' to index an object whose keys are of type string | symbol. Let us go through some of the fixes to this problem.

    // we should begin by defining your interface more explicitly like so
    export interface IData {
      mappings: { 
        car: Array<string>;
        bike: Array<string>;
      };
    }
    
    // you have the data which has keys of type string, and an array as the value
    mappings = {
      "car": ["benz","toyota","ford"],
      "bike" : ["enfield", "pulsar"]
    }
    
    // you then have a function defined which accesses this data
    const getOptions = (value: keyof IData['mappings']) => {
      // now the value has to be a key of the IData mapping interface that you
      // have defined i.e. 'car' | 'bike'
      return contextOptionMappings[value];
    }
    

    UPDATE: If you want to represent the object as something that could have any number of keys then you could do this for one:

    export interface IDate {
      // using a record
      mappings: Record<string, Array<string>>,
      // or using an idiomatic definition
      // mappings: { [key: string]: Array<string> }
    };
    

    This still works but you lose out on more explicit typings.

    I am posting this answer in case you want a more explicit approach to your problem that makes more use of the typings offered by typescript.

    If your interface is more variant than the data you have provided you can still accomodate the cases explicitly.