5

I have been trying to understand when is unsubscribe(the callback in useEffect) gets called exactly.

This is the codepen link : https://codepen.io/deen_john/pen/eYmNdMy Code :

const { useState, useEffect } = React  ;
 
function App() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
    console.log("use effect called ");
    
    return () => {
      console.log("unsubscribe ")
    }
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>

Problem : In my example, the callback function in useEffect hook (i.e unsubscribe) , gets called every time i click on button (i.e every time i update the button state). But, as per the React documentation, callback in useEffect works like componentWillUnmount lifecycle , so in my example it should have been called only if the App component is unmounted. I am just updating the button state here,not unmounting App component on every click.

Deen John
  • 3,522
  • 4
  • 29
  • 32

2 Answers2

5

The unsubscribe event of useEffect is called everytime before the next call to useEffect as well as when the component unmounts

Since in your case, you haven't passed any dependency array to useEffect, your useEffect will run on each render of the component and hence the unsubscribe will run on each re-render before the next useEffect is triggered

A better way to write the above code would be to pass count in the dependency array to useEffect

const { useState, useEffect } = React;
 
function App() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
    console.log("use effect called ");
    
    return () => {
      console.log("unsubscribe ")
    }
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id="root"></div>
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
2

React will always try to keep your effects update. So whenever they run, it will by default try to cleanup the last effect to apply the new one.

  • Effects that are run after every render

So if your effect run after every render, React will cleanup after every render, and then apply the new effect.

  • Effcts that are run based on some dependencies

If your effect depends on some variables (contained in the dependency array [a,b, etc] to decide if it should run again, the same will happen. If it will run again, React will run the cleanup function before running the effect.

  • Effects that are run only after 1st render

If your effect will only run after 1st render (with an empty dependency array []), then React will only do a cleanup after component unmounts.

Example:

The snippet below show that behavior.

function App() {
  console.log('Rendering App...');
  const [show2,setShow2] = React.useState(true);
  const [stateBool,setStateBool] = React.useState(true);
  return(
    <React.Fragment>
      <button onClick={()=>{
        console.clear();
        setStateBool((prevState)=>!prevState)}
      }>
        Update App
      </button>
      <button onClick={()=>{
        console.clear()
        setShow2((prevState)=>!prevState)}
      }>
        Toggle Component2
      </button>
      <Component1/>
      {show2 && <Component2/>}
    </React.Fragment>
  );
}

function Component1() {
  console.log('Rendering Component1...');
  React.useEffect(()=>{
    console.log('useEffect Component1 after every render...');
    return () => console.log('Cleaning up useEffect Component1...');
  });
  return(
    <div>Component1</div>
  );
}

function Component2() {
  console.log('Rendering Component2...');
  React.useEffect(()=>{
    console.log('useEffect Component2 only after 1st render...');
    return () => console.log('Cleaning up useEffect Component2...');
  },[]);
  return(
    <div>Component2</div>
  );
}

ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
cbdeveloper
  • 27,898
  • 37
  • 155
  • 336