I'm using React and a store library with Typescript. Here I have an application type:
type ApplicationState = {
loading: boolean;
data: string[];
colors: number[];
alerts: Alerts;
error: string;
}
Here I have a "setter" what can change the values for each key in the state individually:
const mySetter = (key: keyof ApplicationState, value: Partial<ApplicationState>) => {
return {
...state,
[key]: value,
}
}
Of course, the above throws a type error for the value parameter. The key is simple, I want the keys to be a key of the ApplicationState type. How can I tell typescript that I want the value to be one of the types of ApplicationState (instead of a union type)? I tried Partial and Pick, but typescript still throws errors at me.
Any help is much appreciated.
With the use of generics and indexed access types, this constraint can be enforced in the type signature of mySetter
as below:
<K extends keyof ApplicationState>(key: K, value: ApplicationState[K]) => ApplicationState
K
, constrained to the keys of ApplicationState.key
is simply of type K
. Read more on generics here: https://www.typescriptlang.org/docs/handbook/2/generics.htmlvalue
looks up the type of key K
in
ApplicationState
via an indexed access type. Read more here:
https://www.typescriptlang.org/docs/handbook/2/indexed-access-types.htmlFull code below, demonstrating the behaviour you're after:
type ApplicationState = {
loading: boolean;
data: string[];
colors: number[];
alerts: any;
error: string;
}
const state: ApplicationState = {
loading: false,
data: [],
colors: [],
alerts: {},
error: "some error"
}
const mySetter: <K extends keyof ApplicationState>(key: K, value: ApplicationState[K]) => ApplicationState = (key, value) => {
return {
...state,
[key]: value,
}
}
// The following are happy, value is typed to whatever the type of the key is in ApplicationState type
mySetter("loading", false)
mySetter("data", [])
mySetter("colors", [1, 2])
// The following are unhappy, and will not compile, as desired.
mySetter("loading", "not-a-boolean-value")
mySetter("data", "not-an-array-of-strings")
mySetter("colors", { some: "object" })