A React Typescript Change Handler to Rule Them All
Nick Scialli
April 20, 2020
In React with Typescript, you may be tempted to roll individual change handlers for each field in a component. Today I’ll show you how to avoid this redundant work and write just one change handler!
A Simple Use Case
Here’s a simple use case: we have a form in React with two text inputs and a checkbox input. These inputs will populate a User
object, which will have the following types:
type User = {
name: string;
age: number;
admin: boolean;
};
Let’s see how this might look in the context of a React component. We’ll use the useState
hook to maintain internal state for the user object.
import React, { useState } from 'react';
type User = {
name: string;
age: number | null;
admin: boolean;
};
const defaultUser: User = {
name: '',
age: null,
admin: false,
};
function App() {
const [user, setUser] = useState(defaultUser);
return (
<div>
<input value={user.name} />
<input value={user.age || ''} />
<input type="checkbox" checked={user.admin} />
</div>
);
}
Handling Changes
But how to handle changes for each property? Well, we could create a different change handler for each input, but that would be redundant. Let’s create a single onUserChange
that set’s the correct prop for each input.
import React, { useState } from 'react';
type User = {
name: string;
age: number | null;
admin: boolean;
};
const defaultUser: User = {
name: '',
age: null,
admin: false,
};
function App() {
const [user, setUser] = useState(defaultUser);
const onUserChange = <P extends keyof User>(prop: P, value: User[P]) => {
setUser({ ...user, [prop]: value });
};
return (
<div>
<input
value={user.name}
onChange={(e) => {
onUserChange('name', e.target.value);
}}
/>
<input
value={user.age || ''}
onChange={(e) => {
onUserChange('age', parseInt(e.target.value));
}}
/>
<input
type="checkbox"
checked={user.admin}
onChange={() => {
onUserChange('admin', !user.admin);
}}
/>
</div>
);
}
The secret sauce here is the generic we use in the onUserChange
handler. By saying that prop
is of type P
where P extends keyof User
, we can typecheck the second argument of onUserChange
based on the first argument. Then, when we setUser
, the typescript compiler is happy with our typings.
Nick Scialli is a senior UI engineer at Microsoft.