How to prevent useEffect from running on mount in React
Nick Scialli
June 28, 2021
Sometimes we don’t want a useEffect
hook to run on initial render. This could be for many reasons, but a common use case is when we’re fetching the data on which that hook depends. Initially, the data is empty, so running that hook is pointless.
A theoretical example
In the following component, we fetch
some data and then we intend to doSomething
with that data after it’s been fetched.
function MyComponent() {
const [data, setData] = useState();
// An effect to fetch the data
useEffect(() => {
fetch('/api/some-api')
.then((res) => res.json())
.then((d) => {
setData(d);
});
}, []);
// Do something else with the data
useEffect(() => {
doSomething(data);
}, [data]);
}
On initial render, we fetch
the data, but we also run our second effect.
Important: the useEffect
hook will always run on mount regardless of if there is anything in its dependency array.
We probably don’t want to actually run this effect on our data
when it’s undefined
(as it will be on initial render) but rather we want to wait until it is populated from the API call.
useRef to the rescue!
One solution to this problem is employing the useRef
hook to track whether the component has mounted or not. While a ref
is often thought of as a way to gain reference to DOM elements, it can also be used to refer to other objects and primitives.
In this case, we’ll create a ref
to a boolean that tracks whether the component has mounted. It will start out as false
, but once the effect
runs for the first time, we can change it to true
.
This is probably much easier to understand as code:
function MyComponent() {
const [data, setData] = useState();
const isMounted = useRef(false);
// An effect to fetch the data
useEffect(() => {
fetch('/api/some-api')
.then((res) => res.json())
.then((d) => {
setData(d);
});
}, []);
// Do something else with the data
useEffect(() => {
if (isMounted.current) {
doSomething(data);
} else {
isMounted.current = true;
}
}, [data]);
}
isMounted.current
starts off as false
, so the first runthrough of our second useEffect
hook won’t call doSomething
. Instead, it will set isMounted.current
to true
. On subsequent runs of the hook, isMounted.current
will be true
and doSomething
will be executed on our data.
Why this approach?
There are a few nice things about using a ref
for this purpose:
- We can be assured that on any subsequent run of the hook, our
data
will have been changed—it’s the only value in the dependency array. - By not using state to track
isMounted
, we’re not forcing extra component renders.isMounted.current = true
is mutating ourref
(which is its intended use), and therefore it’s the same object/won’t cause re-renders.
Conclusion
The useEffect
hook can be particularly challenging. By understanding the useRef
hook, we can gain a bit more control about when our effects are run.
Nick Scialli is a senior UI engineer at Microsoft.