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:

  1. The value of quantity increments by 1
  2. The component is re-rendered. quantity is now 1 and totalCost is still 0
  3. The effect is triggered, and totalCost is set to quantity * PRICE_PER_ITEM
  4. The component is re-rendered quantity is still 1 and totalCost is now 5

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:

  1. The value of quantity increments by 1
  2. The component is re-rendered. quantity is now 1 and totalCost is now 5

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:

  1. The component is rendered
  2. The component attempts to display user.name
  3. 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 of user 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 state const [user, setUser] = useState({name: ''})
  • Instead of return <div>Name: {user.name}</div> you could write return <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.