componentWillUnmount with React useEffect hook
How can the useEffect
hook (or any other hook for that matter) be used to replicate componentWillUnmount
?
In a traditional class component I would do something like this:
class Effect extends React.PureComponent {
componentDidMount() { console.log("MOUNT", this.props); }
componentWillUnmount() { console.log("UNMOUNT", this.props); }
render() { return null; }
}
With the useEffect
hook:
function Effect(props) {
React.useEffect(() => {
console.log("MOUNT", props);
return () => console.log("UNMOUNT", props)
}, []);
return null;
}
(Full example: https://codesandbox.io/s/2oo7zqzx1n)
This does not work, since the "cleanup" function returned in useEffect
captures the props as they were during mount and not state of the props during unmount.
How could I get the latest version of the props in useEffect
clean up without running the function body (or cleanup) on every prop change?
A similar question does not address the part of having access to the latest props.
The react docs state:
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run.
In this case however I depend on the props... but only for the cleanup part...
Solution 1:
You can make use of useRef and store the props to be used within a closure such as render useEffect return callback method
function Home(props) {
const val = React.useRef();
React.useEffect(
() => {
val.current = props;
},
[props]
);
React.useEffect(() => {
return () => {
console.log(props, val.current);
};
}, []);
return <div>Home</div>;
}
DEMO
However a better way is to pass on the second argument to useEffect
so that the cleanup and initialisation happens on any change of desired props
React.useEffect(() => {
return () => {
console.log(props.current);
};
}, [props.current]);
Solution 2:
useLayoutEffect() is your answer in 2021
useLayoutEffect(() => {
return () => {
// Your code here.
}
}, [])
This is equivalent to ComponentWillUnmount.
99% of the time you want to use useEffect, but if you want to perform any actions before unmounting the DOM then you can use the code I provided.
Solution 3:
useLayoutEffect
is great for cleaning eventListeners on DOM nodes.
Otherwise, with regular useEffect
ref.current will be null on time hook triggered
More on react docs https://reactjs.org/docs/hooks-reference.html#uselayouteffect
import React, { useLayoutEffect, useRef } from 'react';
const audioRef = useRef(null);
useLayoutEffect(() => {
if (!audioRef.current) return;
const progressEvent = (e) => {
setProgress(audioRef.current.currentTime);
};
audioRef.current.addEventListener('timeupdate', progressEvent);
return () => {
try {
audioRef.current.removeEventListener('timeupdate', progressEvent);
} catch (e) {
console.warn('could not removeEventListener on timeupdate');
}
};
}, [audioRef.current]);
Attach ref to component DOM node
<audio ref={audioRef} />