Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React useEffect cleanup with current props

I ran into a need of cleaning a useEffect when component is unmounted but with an access to the current props. Like componentWillUnmount can do by getting this.props.whatever

Here is a small example: Click "set count" button 5 times and look at your console. You'll see 0 from the console.log in component B, regardless "count" will be 5 https://codesandbox.io/embed/vibrant-feather-v9f6o

How to implement the behavior of getting current props in useEffect cleanup function (5 in my case)?

UPDATE: Passing count to the dependencies of useEffect won't help because:

  1. Cleanup will be invoked on every new count is passed to the props
  2. The last value will be 4 instead of the desired 5
like image 775
Gena Avatar asked Aug 13 '19 16:08

Gena


People also ask

How do I clean up resources in useEffect?

What is the useEffect cleanup function? Just like the name implies, the useEffect cleanup is a function in the useEffect Hook that allows us to tidy up our code before our component unmounts. When our code runs and reruns for every render, useEffect also cleans up after itself using the cleanup function.

Does useEffect work with props?

Just as we were able to set up useEffect to run when a state variable changed, the same can be done with props. Remember they're all regular variables! useEffect can trigger on any of them.

Does useEffect run when props change?

In this case, the side effect will run every time there is a change to the props passed as a dependency. useEffect(() => { // Side Effect }, [props]);

How do you get componentWillUnmount in useEffect?

To clean up after a component unmounts, we have a simple way to perform the equivalent of the componentWillUnmount using the useEffect Hook. The only thing that we need to do is to return a function inside the callback function of the useEffect Hook like this: useEffect(() => { window.


3 Answers

Was referenced to this question from another one so will do a bit of grave digging for it since there is no accepted answer.

The behaviour you want can never happen because B never renders with a count value of 5, component A will not render component B when count is 5 because it'll render unmounted instead of B, the last value B is rendered with will be 4.

If you want B to log the last value it had for count when it unmounts you can do the following:

Note that effects executes after all components have rendered

const useIsMounted = () => {
  const isMounted = React.useRef(false);
  React.useEffect(() => {
    isMounted.current = true;
    return () => (isMounted.current = false);
  }, []);
  return isMounted;
};
const B = ({ count }) => {
  const mounted = useIsMounted();
  React.useEffect(() => {
    // eslint-disable-next-line react-hooks/exhaustive-deps
    return () => !mounted.current && console.log(count);
  }, [count, mounted]);

  return <div>{count}</div>;
};
const A = () => {
  const [count, setCount] = React.useState(0);
  const handleClick = React.useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []);

  if (count === 5) {
    //B will never be rendered with 5
    return <div>Unmounted</div>;
  }

  return (
    <React.Fragment>
      <B count={count} />
      <button onClick={handleClick}>Set count</button>
    </React.Fragment>
  );
};

ReactDOM.render(<A />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
like image 150
HMR Avatar answered Oct 23 '22 23:10

HMR


You need to track count in useEffect:

import React, { useEffect } from "react";

const B = ({ count }) => {
  useEffect(() => {
    return () => console.log(count);
  }, [count]);

  return <div>{count}</div>;
};

export default B;
like image 25
Clarity Avatar answered Oct 23 '22 23:10

Clarity


Since you passed empty array as dependency list for useEffect, your effect function only gets registered the first time. At that time, the value of count was 0. And that is the value of count which gets attached to the function inside useEffect. If you want to get hold of the latest count value, you need your useEffect to run on every render. Remove the empty array dependency.

useEffect(() => {
  return () => console.log(count);
})

or use count as the dependency. That way, your effect function is created anew every time count changes and a new cleanup function is also created which has access to the latest count value.

useEffect(() => {
  return () => console.log(count);
}, [count])
like image 31
Mukesh Soni Avatar answered Oct 24 '22 00:10

Mukesh Soni