The latest @types/react
(v15.0.6
) make use of features added in TypeScript 2.1 for setState
, namely Pick<S, K>
. Which is a good thing, because now the typings are correct, because before the update typings "didn't know" that setState
is merging this.state
, rather than replacing it.
Also, using Pick
makes the setState
function very strict in terms of allowed input. It is no longer possible to add properties to the state
that aren't defined in the component definition (second generic of React.Component
.
But it is also harder to define a dynamic update handler. For example:
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 will throw 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; }'.
even though the type of key
is "name" | "age"
.
I can not find a solution for this, other than having a separate updateName
and updateAge
function. Does anyone know how to use Pick
with dynamic key values?
The answer from Sebastian no longer works for me (though at one stage it did). I now do the following until it is resolved in Typescript core (as per the issue listed in Sebastians answer):
handleUpdate (e:React.SyntheticEvent<HTMLInputElement>) {
const newState = {};
newState[e.currentTarget.name] = e.currentTarget.value;
this.setState(newState);
}
It's lame, but it works
So after doing more research I can provide a little more context on 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 the const
with a let
(let name = 'Bob'
) the variable name
will be of type string
.
This concept is called "widening". Basically, it means that the type system tries to be as explicit as possible. Because const
can not be reassigned TypeScript can infer the exact type. let
statements can be reassigned. Thus, TypeScript will infer string
(in the above example) as the type of name
.
The same is happening with const key = e.currentTarget.name as keyof Person
. key
will be of (union) type "name"|"age"
, which is exactly what we want it to be. But in the expression this.setState({ [key]: value });
variable key
is (incorrectly) widened to a string
.
tl;dr; It seems like there is a bug in TypeScript. I posted the issue to the Github repo and the TypeScript team is investigating the problem. :)
As a temporary workaround you can do:
this.setState({ [key as any]: value });
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With