5

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?

tsuki
  • 155
  • 1
  • 3
  • 9
  • Maybe you didn't understand the question. Will cleanup be performed only if it is returned not from the conditional branches? – tsuki Jan 24 '20 at 08:45
  • thats what exactly mentioned in the documentation. Please do take a look – Akhil Aravind Jan 24 '20 at 08:58
  • Hi @tsuki, I was looking into this as well. I don't know why `cleanup` wasn't being called for you. I made an example codesandbox which proves that cleanup gets called within the conditional - https://codesandbox.io/s/bold-microservice-5895d?file=/src/App.tsx It shows that clean up gets executed with the correct values before the new effect is called. Hope that helps! – CKD Oct 14 '20 at 00:05

2 Answers2

7

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>
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
0

When you return a function in the callback passed to useEffect, the returned function will be called before the component is removed from the UI.

Normally we do cleanups in the componentWillUnmount for class based component. Let's say you want to create an event listener on componentDidMount and clean it up on componentDidUnmount.Like class based with hooks the code will be combined into one callback function.

For cleanup define like this -example

useEffect(()=>{
        let interval = setInterval(()=>{
           // do something 
         }),1000}
    return () => { clearInterval(interval) }
}, [foo])

For better understanding check official Effects with Cleanup

akhtarvahid
  • 9,445
  • 2
  • 26
  • 29
  • This is what I do but the cleanup function is called before the setInterval to finish. I am loading a progress bar in the setInterval. How can I call the clearInterval method only when the operations from the setInterval are done ? – Ionut Enache Jun 21 '22 at 14:53