Reputation: 194
I have this interface defined as my state:
interface State {
id: string;
name: string;
description: string;
dimensionID: string;
file: File | null;
operator: string;
isFormValid: boolean;
filename: string;
};
I have a simple on change handler:
update = (event: React.FormEvent<HTMLInputElement>): void => {
const { name, value } = event.currentTarget;
this.setState({ [name]: value });
};
However, this error gets thrown:
Error:(109, 19) TS2345: Argument of type '{ [x: string]: string; }' is not assignable to parameter of type 'State | Pick<State, "id" | "name" | "description" | "dimensionID" | "file" | "operator" | "isFormValid" | "filename"> | ((prevState: Readonly<State>, props: Readonly<Props>) => State | Pick<...>)'.
Type '{ [x: string]: string; }' is not assignable to type '(prevState: Readonly<State>, props: Readonly<Props>) => State | Pick<State, "id" | "name" | "description" | "dimensionID" | "file" | "operator" | "isFormValid" | "filename">'.
Type '{ [x: string]: string; }' provides no match for the signature '(prevState: Readonly<State>, props: Readonly<Props>): State | Pick<State, "id" | "name" | "description" | "dimensionID" | "file" | "operator" | "isFormValid" | "filename">'.
My question is: How can I set state dynamically like how my on change handler tries? This handler is used for different parts of the form so I won't know which key of state I will need to update.
Update: SOLUTION From this post
update = (event: React.FormEvent<HTMLInputElement>): void => {
const { name, value } = event.currentTarget;
this.setState(prevState => ({
...prevState,
[name]: value,
}))
};
Works!
Upvotes: 10
Views: 11011
Reputation: 2225
Most prefered solution casting.
this.setState({[name]: value} as {[P in keyof State]: State[P]})
// or
this.setState({[name]: value} as Pick<State, keyof State>)
setState with spread previous state
this.setState((prevState) => ({ ...prevState, [name]: value }));
This doesn't check the types. It's just a quick workaround for anyone who is stuck transpiling.
this.setState<never>({[name]: value})
Related links
Upvotes: 3
Reputation: 1485
You can use the in keyof
keywords to deconstruct the key / value from the IState object.
Create your React state:
interface IState { "email" : string; }
Create a form type that deconstruct the key as K and value as T[K]:
type FormState<T> = { [K in keyof T]: T[K] };
// T represents your IState
// to get the key use: [K in keyof T]
// to reference the value use: T[K]
Then pass the IState as a generic type argument to FormState type:
FormState<IState>
Now you can update the state, casting it to the above FormState:
this.setState({ [formStateItem]: (e.target.value as any) } as FormState<IState>);
In a handler it might look like this:
handleChange = <T, K extends keyof IState>(formStateItem: K) => {
return (e: ChangeEvent<HTMLInputElement>) => {
this.setState({ [formStateItem]: (e.target.value as any) } as FormState<IState>);
}
};
And finally you can use in you JSX like this:
onChange={this.handleChange("email")
Upvotes: 0
Reputation: 2352
The constant name is widened to string. Which version of TS are you using?
You cast it like this, but it's not a good practice though:
this.setState({
[name]: value
} as Pick<State, keyof State>);
Bug related to this issue:
Upvotes: 2
Reputation: 775
eg.
update = (name: string, event: React.FormEvent<HTMLInputElement>): void => {
const { value } = event.currentTarget;
this.setState({ [name]: value });
};
<input onChange={this.update.bind(null, 'first_name')} value={this.state.first_name} />
Upvotes: 1