Avoiding repetitive state in React Hooks

When using React Hooks, you’ll eventually come across a scenario where you need to define repetitive state variables. This makes your code feel a bit dirty:

function Example () {
const [firstName, setFirstName] = useState();
const [lastName, setLastName] = useState();
const [address1, setAddress1] = useState();
const [address2, setAddress2] = useState();
const [zipCode, setZipCode] = useState();

return (
<form>
<input
value={firstName}
onChange={e => setFirstName(e.target.value)}
/>

<input
value={lastName}
onChange={e => setLastName(e.target.value)}
/>

<input
value={address1}
onChange={e => setAddress1(e.target.value)}
/>

<input
value={address2}
onChange={e => setAddress2(e.target.value)}
/>

<input
value={zipCode}
onChange={e => setZipCode(e.target.value)}
/>

</form>
);
}

This isn’t the worst code we could’ve written, but we can make cleaner. One of the big benefits of React Hooks is that we can abstract this kind of repetitive state definition into its own function.

Let’s write a function called useFormField. (It can live in the same file as our other component for now.) This function will have the same markup as a typical React component, except instead of returning JSX, it’ll return an object with a value and onChange function.

function useFormField() {
const [value, setValue] = useState();

const handleChange = e => setValue(e.target.value);

return { value, onChange: handleChange };
}

This tiny function is what we call a custom hook, and we can use it in place of useState:

// Instead of:
const [firstName, setFirstName] = useState();

// We can use:
const firstName = useFormField();

What this is actually saying is that firstName now contains an object with a value and an onChange function, so we can use JavaScript’s spread syntax to spread this object over your input as parameters:

function Example () {
const firstName = useFormField();
const lastName = useFormField();
const address1 = useFormField();
const address2 = useFormField();
const zipCode = useFormField();

return (
<form>
<input {...firstName} />
<input {...lastName} />
<input {...address1} />
<input {...address2} />
<input {...zipCode} />
</form>
);
}

If you’re confused, I explain spread syntax here:

JavaScript spread syntax explained in 30 seconds

In addition to that, I have two more tips for using this pattern. One is that custom hooks are simply functions, which means that you can give them arguments. For instance, let’s say we wanted to pre-fill the first name with “Mark”:

const firstName = useFormField("Mark");
function useFormField(initialValue) {
const [value, setValue] = useState(initialValue);
// ...
}

And finally, if you want to define a custom property per input field, you could just add it after the spread:

<input {...firstName} placeholder="Ryuji" />

React Hooks can be a little intimidating, but hopefully this sheds some light on how to deal with one of their common scenarios. Split repetitive state definitions into their own hooks, and your code will become much cleaner. Hope you enjoy.

I'm Mark Thomas Miller, an engineer, designer, and maker of things.