Home > Mobile >  TS Record type: ...expression of type 'HeaderName' can't be used to index type '
TS Record type: ...expression of type 'HeaderName' can't be used to index type '

Time:07-19

The subject line says it all. I have a record type Record<HeaderName, str> and I am trying to add to the Record object by indexing. headerMap['name'] = 'somename'

In a typescript class:

headerMap: Record<HeaderName, string> | {} = {};

Now when I want to pre-populate it, it gets mad at the type but I bend over backwards to convince the compiler it's the right type.

(Object.keys(this.required) as HeaderName[]).forEach(k => {
  // iterate through csv headers
  this.csvHeaders.forEach(c => {
      let typedK: HeaderName = 'name' // ok
      if (k.toLowerCase().includes(c.toLowerCase())) {
      this.headerMap[k as HeaderName] = c // error below
      this.headerMap[typedK] = c // also error
      }

    }
  });
});

Element implicitly has an 'any' type because expression of type 'HeaderName' can't be used to index type '{} | Record<HeaderName, string>'.
  Property 'name' does not exist on type '{} | Record<HeaderName, string>'.Vetur(7053)

But 'name' is one of the types:

export type HeaderName =
  | 'name'
  | 'medicaid_id'
  | 'external_id'
...

What's more confusing, is that it magically works in the Vue template

  <v-row align="center" v-for="(header, headerName) in required" :key="headerName">
    <v-col>
      {{ snakeToTitle(headerName) }}
      <small v-if="!header.is_optional" >*</small>
    </v-col>
    <v-col>
      <ih-auto-complete
        v-model="headerMap[headerName]". // THIS JUST WORKS!
        :items="csvHeaders"
        item-value='DOB'
        item-text='text'
        :vid="headerName"
        :required="!header.is_optional"
        :rules="header.is_optional ? '' : 'required'"
      />
    </v-col>
  </v-row>

How do you do an insert into a Record<K, str> | {} if not foo[k] = s?

It should be valid even accounting for runtime vs compile time, but I must be missing something. BTW I studied other questions and articles and the docs, but they don't seem to address this particular case.

HeaderName type

CodePudding user response:

Let's simplify the problem:

type ab = 'a' | 'b';
let headerMap: Record<ab, string> | {};
headerMap['a'];
// Element implicitly has an 'any' type because expression of type '"a"' can't be used to index type '{} | Record<ab, string>'.
//   Property 'a' does not exist on type '{} | Record<ab, string>'.

Pay attention to the last line of the error: Property 'a' does not exist on type '{} | Record<ab, string>'. You defined the headerMap type as {} | ..., meaning {} MIGHT be the type of headerMap. And, you can not access property 'a' of type {}, since {} has no properties defined (DIFFERENT from a type with optional properties, more on that later). In order to fix this, let's just remove the {} type from the union.

declare let headerMapFix: Record<ab, string>;
headerMapFix['a'];

Wow! No errors! However, I assume you already thought about this, but didn't go through, because you wanted the headerMap to be by default an empty object:

let headerMapFix2: Record<ab, string> = {};
headerMapFix2['a'];
// Type '{}' is missing the following properties from type 'Record<ab, string>': a, b

Error! This is because Record<ab, string> is a shorthand for {a: string, b: string}, so both properties are mandatory. Ideally, we want a type that represents {a?: string, b?: string}. Luckily, there is a type for that: Partial<Record<ab, string>>. Partial makes all the properties of the wrapped object type optional. Let's try again:

let headerMapFix3: Partial<Record<ab, string>> = {};
headerMapFix3['a'];

No errors! And of course, you could assign a value to it, headerMapFix3['a'] = undefined or headerMapFix3['a'] = 'some string';

Notice how type {a?: string, b?: string} is different from type {}, because although a and b can be undefined in the former, at least it is declared that the property exists and can be accessed even if it's undefined. {} defined no properties to access, which is why you couldn't access any properties on your attempt.

  • Related