Avoid This Common React Hook Antipattern
When displaying synchronous data in React components, useState
and useEffect
are antipatterns that should be avoided. Using useState
and useEffect
to compute synchronous values will lead to a sub-optimal user experience and subtle bugs that can be hard to debug.
The Problem
Consider the following component:
import React, { useState, useEffect } from 'react';
interface Props {
firstName: string;
lastName: string;
}
function MyComponent(props: Props) {
const { firstName, lastName } = props;
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
return (
<span>{fullName}</span>
);
}
In this example we are computing fullName
from two props: firstName
and lastName
. Even though firstName
and lastName
are both available at render time, we are passing them both into a useEffect
hook. Inside the useEffect
hook, we are then concatenating the values and then calling setFullName
with the concatenated value.
This is problematic for several reasons:
useEffect
runs after the component renders and displays a value to the user. This means that the user will always see a stale value before the updated value is shown to the user.- To see our computed value, we have to always render our component twice. On the first render, we display a stale value. When the
useEffect
runs, it callssetFullName
which will trigger another render. - From a developer perspective, the logic is hard to read and hard to follow.
The fix
The fix for this pattern is simple. Instead of computing the values in a useEffect
, we can simply compute the values as part of the “natural” rendering process:
import React from 'react';
interface Props {
firstName: string;
lastName: string;
}
function MyComponent(props: Props) {
const { firstName, lastName } = props;
const fullName = `${firstName} ${lastName}`;
return (
<span>{fullName}</span>
);
}
Computing the value during render has the following advantages:
- Our component is much simpler. We were able to completely eliminate any hook usage in our component.
- The user will never see a stale value. Everytime one of our props changes, it will be immediately reflected in the DOM.
- We’ve eliminated an extra render cycle.
The takeaway
When you need to display computed value from synchronous data in React, it is almost always easier and safer to just compute the value in the component’s body without useEffect
or useState
. This will lead to simpler components and a better user experience!
More Reading
Interested to learn about Typescript? Understanding Typescript’s Type Predicates