[SOLVED] Property does not exist on type / can't be used as index type

Issue

I want to have an object that can contain strings in its root, and an array of strings in the arrayValues object. This is a config object that will be used in an app.

This object and its keys will be dynamic (defined via user state values) so I have no way of specifying types based on the specific key name.

I will be adding arrays to "arrayValues" from one part of my app, and strings to "filters" from another part of the app.

However I’m getting errors when accessing object properties:
TS PLAYGROUND

export type FiltersStateType = {
  filters:
    | {
        arrayValues: { // note: this should only appear once in filters object
          [key: string]: Array<string>
        }
      }
    | { [key: string]: string }
}

const example: FiltersStateType = {
  filters: {
    arrayValues: {
      sandwiches: ['ham', 'cheese'],
      milkshakes: ['choc', 'banana'],
    },
    randomString: 'hello',
  },
}

example.filters.arrayValues.sandwiches.filter((item: string) => item !== 'ham') 
// ❌ Property 'sandwiches' does not exist on type 'string | { [key: string]: string[]; }'.
// ❌ Property 'sandwiches' does not exist on type 'string'

const stringKey = 'milkshakes'
example.filters.arrayValues[stringKey].filter((item: string) => item !== 'choc') 
// ❌ Element implicitly has an 'any' type because expression of type '"milkshakes"' can't be used to index type 'string | { [key: string]: string[]; }'.
// ❌ Property 'milkshakes' does not exist on type 'string | { [key: string]: string[]; }'

example.filters.randomString = 'bye'
// ❌ Property 'randomString' does not exist on type '{ arrays: { [key: string]: string[]; }; } | { [key: string]: string; }'.
// ❌ Property 'randomString' does not exist on type '{ arrays: { [key: string]: string[]; }; }

I expected TypeScript to understand the types found in these objects as I’ve specified them in FiltersStateType.

Do you know why this is happening?

Solution

This is happening because of the highlighted Union:

export type FiltersStateType = {
  filters:
    | {
        arrayValues: {
          [key: string]: Array<string>
        }
      }
    | { [key: string]: string } // this one
// ---^^^^^^^^^^^^^^^^^^^^^^^^^---
}

As you have specified { [key: string]: string } in the union, the below object stands valid:

const example: FiltersStateType = {
  filters: {
    arrayValues: 'hello',
  },
}

TypeScript is unable to enforce the shape of the value of arrayValues key, as { [key: string]: string } itself means any key can have value of string. Ideally, you must change the structure of your type definitions something like this:

// this is just an example, you should
// restructure as per your requirements 
type FiltersStateType = {
  filters: { [key: string]: string },
  arrayValues: { [key: string]: Array<string> }
}

Answered By – Vinay Sharma

Answer Checked By – Gilberto Lyons (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *