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.