I encountered a situation where my React component had numerous methods for toggling boolean state properties. Since these functions all did the same thing, I wanted to streamline the process by creating a common function for toggling properties.
Each method followed this structure:
toggleProperty() {
this.setState(previous => ({
myProperty: !previous.myProperty
}))
}
To simplify things, I devised a universal method that could be invoked with the relevant context:
/** Filters out keys of B that doesn't have the same type of T **/
type FilterOutUnmatchedType<B extends Object, T extends any> = {
[K in keyof B]: B[K] extends T ? K : never;
}[keyof B];
private StateToggler(this: Configurator, property: FilterOutUnmatchedType<ConfiguratorState, boolean>) {
this.setState((previous) => ({
[property]: !previous[property]
});
}
In this method, my goal was to only accept boolean properties from the state object. While FilterOutUnmatchedType
served its purpose, I encountered an error in Visual Studio Code:
Argument of type '(previous: Readonly<ConfiguratorState>) => { [x: string]: boolean; }' is not assignable to parameter of type 'ConfiguratorState | ((prevState: Readonly<ConfiguratorState>, props: Readonly<ConfiguratorProps>) => ConfiguratorState | Pick<...>) | Pick<...>'.
Type '(previous: Readonly<ConfiguratorState>) => { [x: string]: boolean; }' is not assignable to type '(prevState: Readonly<ConfiguratorState>, props: Readonly<ConfiguratorProps>) => ConfiguratorState | Pick<...>'.
Type '{ [x: string]: boolean; }' is not assignable to type 'ConfiguratorState | Pick<ConfiguratorState, (omitted, keyof ConfiguratorState - basically))>'
It appeared that the code was considering [property]: boolean
too generic, even though property
was within keyof ConfiguratorState
.
After multiple attempts to resolve this issue, I stumbled upon a solution that seemed to work without clear explanation. The syntax involved a casting as part of Mapped Types:
this.setState(
(previous) => ({
[property]: !previous[property]
} as { [K in keyof ConfiguratorState] })
);
This usage of Mapped Types caught me off guard, as I initially expected a right-hand side assignment. Surprisingly, omitting the RHS like ConfiguratorState[K]
resulted in the desired outcome. Though perplexing, the code executed correctly.
If anyone can shed light on this concept and direct me to additional resources, I would greatly appreciate it!