React's useEffect
hook is an important tool for managing side effects in your functional components.
In this post, we'll look at when your effects run.
Effects run on the initial render
Every effect runs at least once when your component first renders. In the example below, the log statement in the useEffect
hook executes without you having to do anything:
Effects run every render if there are no dependencies
In the previous example, we passed an empty array as the second argument to the useEffect
hook. This argument is called the dependency array. The array controls when the effect runs after the first render.
If you don't pass a second argument to the useEffect
hook, your effect will run on every render. In the example below, the log statement runs every time you click the Force Rerender button:
Effects run when dependencies change
If you want to be more selective about when your effect runs, you need to use the dependency array argument. The way it works is that the values in the dependency array are compared every render. If a value at a given position changes, the effect is executed again.
Let's take a look at an example:
// First render
useEffect(() => {}, [1, 2]);
// Second render
useEffect(() => {}, [1, 3]);
Dependency array on the first render:
[1, 2]
Dependency array on the second render:
[1, 3]
The first element (1
) is the same between renders, but the second element (2
and 3
) is different. The effect is re-executed because the value changed for the second element in the dependency array.
In the example below, we have a count
variable managed by React state. Every time you click the Count button, the value is incremented, and the component re-renders.
Since the count
variable passed to the useEffect
dependency array changes between renders, the log statement will run.
Now what if we click the Force Rerender button instead?
This button doesn't update the count
variable. Even though the component re-renders, the value passed to the useEffect
dependency array is the same. In this case, the log statement won't run.
Try it out!
Non-primitive data types are compared by reference
One thing to keep in mind is that any non-primitive data type passed to a dependency array is compared by its reference.
This means that even if a value looks the same, if the underlying reference changes, React will execute the effect.
For example, the two functions below may look the same, but from JavaScript's perspective, they have two different function references:
const fnOne = () => true;
const fnTwo = () => true;
fnOne === fnTwo // false (different references)
Here are a few data types where this applies:
Functions
Objects
Arrays
We can see this play out in the example below.
Since the logger
function is recreated every render, the underlying reference will be different.
This time, since we pass the logger
in the dependency array, the Force Rerender button will cause the log statement to run again:
Create stable references to prevent effects from running
We can pass stable references to our dependency arrays to solve this problem.
For functions that are created in a React component, this usually means wrapping it with the useCallback
hook.
The useCallback
hook takes a function as its first argument and a dependency array as the second argument. On the initial render, a new function is created. However, every other render will return the same function if the elements in the dependency array are the same.
The useCallback
dependency array follows the same rules as the useEffect
dependency array.
This time the logger
function is wrapped with the useCallback
hook, so we get a stable reference between renders and the Force Rerender button won't cause the log statement to run again:
Effects can have cleanup functions
Another important part about effects is that they have optional cleanup functions. If you return a function in an effect, React will execute it before executing the next effect. In other words, it'll look something like this:
The effect runs on the initial render
A value in the dependency array changes
The cleanup function from the first effect runs
The next effect runs and returns a new cleanup function
In the example below, you'll notice that a Clean up Count
log statement runs before the next Count
log statement every time you click the Count button.
And that's it!
A quick look at when React's useEffect
hook runs.
Understanding when the useEffect
hook runs will help you manage side effects predictably and efficiently, ensuring that your React components behave as expected!