Top useEffect mistakes made by React Developers
- Jun 13, 2023
useEffect is a kind of “escape hatch” which lets you perform side effects in functional components.
Effects let you step outside of React, giving you the ability to synchronise your components with systems that aren’t controlled by React such as a non-react widget, an external system (such as a browser API), a third party library, or a network connection.
Effects aren’t always necessary; if there is no external system involved, you shouldn’t need useEffect.
But it isn’t always clear when effects are necessary, and how to use them. Which leads to some common mistakes.
Here are some of the top mistakes that I’ve seen people make with useEffect:
Using an effect instead of deriving state
The biggest mistake that some React developers make is unnecessarily using an effect. For example:
function NotDerivingState() {
const PRICE_PER_ITEM = 5;
const [quantity, setQuantity] = useState(0);
// Avoid: redundant state and unnecessary effect
const [totalCost, setTotalCost] = useState(0);
useEffect(() => {
setTotalCost(quantity * PRICE_PER_ITEM);
}, [quantity]);
return (
<>
<div>Total items: {quantity}</div>
<div>Total cost: {totalCost}</div>
<button onClick={() => setQuantity(quantity + 1)}>Add an item</button>
</>
)
}
When the “Add an item button” is clicked, the following happens:
- The value of
quantity
increments by 1 - The component is re-rendered.
quantity
is now1
andtotalCost
is still0
- The effect is triggered, and totalCost is set to
quantity * PRICE_PER_ITEM
- The component is re-rendered
quantity
is still1
andtotalCost
is now5
But the effect is unnecessary. Not only do you have excess renders, but you have an in-between render which displays values that are not correct, where quantity
is 1
but totalCost
remains 0
Instead, the React team recommends that you derive state whenever possible. This is also true if you are using some kind of state management such as Redux.
Consider this component which derives totalCost from quantity and PRICE_PER_ITEM:
function DerivingState() {
const PRICE_PER_ITEM = 5;
const [quantity, setQuantity] = useState(0);
// Good: Calculated rendering
const totalCost = quantity * PRICE_PER_ITEM;
return (
<>
<div>Total items: {quantity}</div>
<div>Total cost: {totalCost}</div>
<button onClick={() => setQuantity(quantity + 1)}>Add an item</button>
</> )
}
When the “Add an item button” is clicked, the following happens:
- The value of quantity increments by 1
- The component is re-rendered.
quantity
is now1
andtotalCost
is now5
Not only do we have one less render, the component is now much simpler and less error prone.
Forgetting that effects are executed after the component is rendered
Consider the following component which fetches some dummy data from https://jsonplaceholder.typicode.com/ and attempts to display user.name
:
function EffectRunsAfterRender() {
const [user, setUser] = useState();
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(response => response.json())
.then(json => setUser(json));
});
return <div>Name: {user.name}</div>
}
Here’s what happens:
- The component is rendered
- The component attempts to display
user.name
- An error will be logged to the console such as
Uncaught TypeError: Cannot read properties of undefined (reading 'name').
This error occurs because effects are run after rending, and the initial state ofuser
is undefind.
The error can be fixed in a variety of ways. Essentially you’ll want to not depend on user being defined until it’s assigned:
- Give
user
an initial stateconst [user, setUser] = useState({name: ''})
- Instead of
return <div>Name: {user.name}</div>
you could writereturn <div>Name: {user?.name}</div>
- Or perhaps a custom component which displays a loading indicator
return user ? <div>Name: {user?.name}</div> : <LoadingIndicator />
- etc
You might not need an effect anyway
In the early days of hooks being introduced, useEffect was much more common.
Nowadays a lot of the legitimate uses of useEffect are wrapped up in common libraries. An example of this is Tanstack Query which uses useEffect in baseQuery and in several other places.
The React team have also published an article of cases where you might not need useEffect: https://react.dev/learn/you-might-not-need-an-effect
Thanks for reading!
Enjoyed this article? Follow me on Twitter for more like this.
Do you have any questions, feedback, or anything that you think I’d be interested in? Please leave a comment below, or get in touch with me directly.