Select type <S, K> with dynamic / computed keys
The latter @types/react
( v15.0.6
) uses functions added in TypeScript 2.1 for setState
, namely Pick<S, K>
. This is good, because now the correct examples are correct, because before the update icons "did not know" that setState
combines this.state
rather than replacing it.
In addition, using Pick
makes setState
very strict in terms of valid input. It is no longer possible to add properties to state
that are not defined in the component definition (second React.Component
family React.Component
.
But it is also more difficult to define a dynamic update handler. For instance:
import * as React from 'react'; interface Person { name: string; age: number|undefined; } export default class PersonComponent extends React.Component<void, Person> { constructor(props:any) { super(props); this.state = { name: '', age: undefined }; this.handleUpdate = this.handleUpdate.bind(this); } handleUpdate (e:React.SyntheticEvent<HTMLInputElement>) { const key = e.currentTarget.name as keyof Person; const value = e.currentTarget.value; this.setState({ [key]: value }); } render() { return ( <form> <input type="text" name="name" value={this.state.name} onChange={this.handleUpdate} /> <input type="text" name="age" value={this.state.age} onChange={this.handleUpdate} /> </form> ); } }
The setState
function throws the following error:
[ts] Argument of type '{ [x: string]: string; }' is not assignable to parameter of type 'Pick<Person, "name" | "age">'. Property 'name' is missing in type '{ [x: string]: string; }'.
although the key
type is "name" | "age"
"name" | "age"
.
I can not find a solution for this, except for the existence of a separate function updateName
and updateAge
. Does anyone know how to use Pick
with dynamic key values?
So, after doing more research, I can provide a little more information about what is happening in the above code.
When you do something like const name = 'Bob'
, the type of the variable name
is 'Bob'
not string. However, if you replace const
with let
( let name = 'Bob'
), the name
variable will be of type string
.
This concept is called "extension". This basically means that the type system is trying to be as explicit as possible. Since const
cannot be reassigned, TypeScript can infer the exact type. let
statements can be reassigned. Thus, TypeScript will output string
(in the example above) as the type name
.
The same thing happens with const key = e.currentTarget.name as keyof Person
. key
will be of type (union) "name"|"age"
, which is what we want. But in the expression this.setState({ [key]: value });
the key
variable (incorrectly) expanded to string
.
tl; dr; There seems to be a bug in TypeScript. I submitted this issue to the Github repository, and the TypeScript team investigates the issue . :)
As a temporary workaround, you can:
this.setState({ [key as any]: value });