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.
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.