useEffect as componentWillUnmount

Viewed 1045

I am trying to refactor an old class component into a functional component with hooks and have got stuck with a componentWillUnmount

Previously the code had this:

 componentDidMount() {    
    this.isDropdownMounted = true;
  } 


  componentWillUnmount() {      
    this.isDropdownMounted = false;
  }

My solution was to use to use a useEffect with a cleanup function but despite it 'appearing' to work it failed code review and I can't seem to find a better solution. I've read about potentially using a useRef but haven't stumbled across a similar use case just yet.

  useEffect(() => {
    isDropdownMounted = true;

    return function cleanup() {
      isDropdownMounted = false;
    };
  }, []);

Any ideas what I can try?

2 Answers

useEffect lets you return a cleanup function that will run whenever your component unmounts.

NOTE: It will also run whenever something in the dependency array of the useEffect changes. It assures you that you'll always get a "fresh" effect.

React docs on useEffect with cleanup.

Here is the example they use:

Using classes:

componentDidMount() {
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

componentWillUnmount() {
  ChatAPI.unsubscribeFromFriendStatus(
    this.props.friend.id,
    this.handleStatusChange
  );
}

Using hooks:

useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  // Specify how to clean up after this effect:
  return function cleanup() {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
});

Here is a working example:

function App() {

  const [boolean,setBoolean] = React.useState(true);
  
  const toggleBoolean = () => setBoolean((prevState) => !prevState);

  return(
    <div>
     { boolean ?
         <Component1/>
       : <Component2/>
     }
       <button onClick={toggleBoolean}>Toggle</button>
     </div>
  );
}

function Component1() {

  React.useEffect(() => {
    console.log("Component1 has mounted...");
    return () => { console.log("Component1 has unmounted...")};
  },[]);

  return(
    <div>Component1</div>
  );
}

function 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"/>

React doesn't remember isDropdownMounted variable. It will be recreated on each render. It will be better to use useRef hook to set value in useEffect and remember it on the next render.

const isDropdownMounted = useRef(null);

useEffect(() => {
    isDropdownMounted.current = true;

    return function cleanup() {
      isDropdownMounted.current = false;
    };
  }, []);
Related