Beyond the Basics: React.useEffect in-depth
If you use React, you can't go a day without writing a useEffect. But do you really understand how it works? In this article, we'll take a closer look at useEffect, provide a troubleshooting guide, and finally find out why it runs 2 times.
useEffect(setup, dependencies?)
The useEffect hook is used for performing side effects (not the component output directly).
Call useEffect at the top level of your component to declare an Effect:
Parameters
Returns
useEffect returns undefined.
Caveats
useEffect under the hood
It's pretty straightforward:
The rendering phase
The re-rendering phase
Most common issues
1️⃣ My Effect runs twice when the component mounts
When Strict Mode is on, in development, React runs setup+cleanup one extra time before the actual setup.
Recommended by LinkedIn
This is a stress-test that verifies that your Effect’s logic is implemented correctly. If this causes visible issues, your cleanup function is missing some logic. The cleanup function should stop or undo whatever the setup function was doing. As a rule of thumb, the user shouldn’t be able to tell the difference between a single setup call (as in production) and a setup → cleanup → setup sequence (as in development).
2️⃣ My Effect runs after every re-render
First, check that you haven’t forgotten to specify the dependency array.
If you’ve specified the dependency array but your Effect still re-runs in a loop, it’s because one of your dependencies is different on every re-render.
You can debug this problem by manually logging your dependencies to the console.
You can then right-click on the arrays from different re-renders in the console and select “Store as a global variable” for both of them. You can then use the browser console to check whether each dependency in both arrays is the same using Object.is.
When you find the dependency that is different on every re-render, you can usually fix it in one of the following ways:
As a last resort, wrap its creation with useMemo or useCallback (for functions).
3️⃣ My Effect keeps re-running in an infinite cycle
If your Effect runs in an infinite cycle, these two things must be true:
Before you start fixing the problem, ask yourself:
If there is no external system, consider whether removing the Effect altogether would simplify your logic.
If you’re really synchronising with an external system, think about why and under what conditions your Effect should update the state. Has something changed that affects your component’s visual output? If you need to keep track of some data that isn’t used by rendering, a ref (which doesn’t trigger re-renders) might be more appropriate. Check that your effect isn't updating the state (and triggering re-renders) more than necessary.
Finally, if your Effect is updating the state at the right time, but there's still a loop, it’s because that state update causes one of the Effect’s dependencies to change.
4️⃣ My cleanup logic runs even though my component didn’t unmount
The cleanup function runs not only during unmount, but also before each re-render with changed dependencies. Additionally, in development, React runs setup+cleanup one extra time immediately after component mounts.
If you have cleanup code without corresponding setup code, it’s usually a code smell.
Your cleanup logic should be "symmetrical" to the setup logic, and should stop or undo whatever the setup did:
5️⃣ My Effect does something visual, and I see a flicker before it runs
If your Effect must block the browser from painting the screen, replace useEffect with useLayoutEffect. Note that this shouldn’t be necessary for the vast majority of Effects. You’ll only need this if it’s crucial to run your Effect before the browser paint: for example, to measure and position a tooltip before the user sees it.