So, I have an useEffect
like this:
useEffect(()=>{
if(foo) {
// do something
return () => { // cleanup function }
}
}, [foo])
Here, the cleanup
function is never called, even if if
block is executed. But if I modify the effect to be:
useEffect(()=>{
if(foo) {
// do something
}
return () => { // cleanup function }
}, [foo])
it works.
So, is the cleanup done only if return
is the last statement of useEffect
or is there something I am missing?
The cleanup function is recreated every time the useEffect
updater function is called. The current cleanup function would be called when the dependency changes (or on each render if no dependency), or before the component is unmounted. After the cleanup is called, the updater would be called, and a new cleanup function can be returned.
You can return a different function, or none at all, whenever the updater function is called.
For example, click the Inc button multiple times, and you can see in the console that the cleanup function exists only for even counter
numbers, because it's returned conditionally.
const { useState, useEffect } = React;
const Demo = () => {
const [counter, setCount] = useState(0);
useEffect(() => {
console.log(`Effect ${counter}`);
if(counter % 2 === 0) {
return () => console.log(`Cleanup ${counter}`);
}
}, [counter]);
return (
<div>
<p>Counter: {counter}</p>
<button onClick={() => setCount(counter + 1)}>Inc</button>
</div>
);
};
ReactDOM.render(
<Demo />,
root
);
.as-console-wrapper { top: 0; left: 50% !important; max-height: unset !important; }
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
Sometimes, nesting the cleanup function inside a condition might be confusing. Another option is to always return a cleanup function, and put the logic inside it:
useEffect(()=>{
if(foo) {
// do something
}
return () => {
if(foo) {
// cleanup something
}
}
}, [foo])
And you can see in this example, the result is the same:
const { useState, useEffect } = React;
const Demo = () => {
const [counter, setCount] = useState(0);
useEffect(() => {
console.log(`Effect ${counter}`);
return () => {
if(counter % 2 === 0) {
console.log(`Cleanup ${counter}`);
}
};
}, [counter]);
return (
<div>
<p>Counter: {counter}</p>
<button onClick={() => setCount(counter + 1)}>Inc</button>
</div>
);
};
ReactDOM.render(
<Demo />,
root
);
.as-console-wrapper { top: 0; left: 50% !important; max-height: unset !important; }
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>