I looking for best way manage state of form in React with TypeScript. My simple form have two values: login
and password
fields. I implement IState
interface for form state and DRY handleChange
method for store new value in state without recreating function in each render()
execution.
interface IState {
login: string;
password: string;
}
class LoginForm extends React.Component<{}, IState> {
public state = {
login: "",
password: "",
};
public render() {
const { login, password } = this.state;
return (
<form>
<input name="login" value={login} onChange={this.handleChange}/>
<input name="password" value={password} onChange={this.handleChange} type="password"/>
</form>
);
}
private handleChange = (e: React.FormEvent<HTMLInputElement>) => {
const { name, value } = e.currentTarget;
const n = name as keyof IState;
// @ts-ignore
this.setState({
[n]: value,
});
}
}
I use native HTML's name
attribute for store field name. n
variable will have only login
or password
value. Any other value is impossible. Can I tell TypeScript the n
variable is "login" | "password"
literals type? TypeScript regard n
as string
type variable even I use:
const n = name as keyof IState;
or
const n = name as "login" | "password";
Then, when I remove // @ts-ignore
I get error:
Type error: Argument of type '{ [x: string]: string; }' is not assignable to parameter of type 'IState | Pick<IState, "login" | "password"> | ((prevState: Readonly<IState>, props: Readonly<IProps>) => IState | Pick<IState, "login" | "password"> | null) | null'.
Type '{ [x: string]: string; }' is missing the following properties from type 'Pick<IState, "login" | "password">': login, password TS2345
but no error when I hardcode:
const n = "login";
Any way to force "login" | "password"
type to n
variable? Or any other solution for DRY handleChange
without optimization issues and pure TypeScript?
We can use Pick
to ensure that you're setting a key that has been defined in your IState
interface.
private handleChange = (e: React.FormEvent<HTMLInputElement>) => {
const { name, value } = e.currentTarget;
this.setState({
[name]: value
} as Pick<IState, keyof IState>);
};
Or alternative you can use Partial
which will make all your state keys optional.
class App extends React.Component<{}, Partial<IState>> {
// ... rest of component
}