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?
CodePudding user response:
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> }
}
CodePudding user response:
You can declare your FiltersStateType
type like this:
type FiltersStateType = {
filters: {
[key: string]: string | {[key: string]: string[]};
arrayValues: { // can only appear once in filters object
[key: string]: string[];
}
}
}
const example: FiltersStateType = {
filters: {
arrayValues: {
sandwiches: ['ham', 'cheese'],
milkshakes: ['choc', 'banana'],
},
randomString: 'hello',
},
}
example.filters.arrayValues.sandwiches = example.filters.arrayValues.sandwiches.filter((item: string) => item !== 'ham');
console.log(example.filters.arrayValues.sandwiches);
const stringKey = 'milkshakes'
example.filters.arrayValues[stringKey] = example.filters.arrayValues[stringKey].filter((item: string) => item !== 'choc');
console.log(example.filters.arrayValues[stringKey]);
example.filters.randomString = 'bye';
console.log(example.filters.randomString);