Note: How to simulate componentWillUnmount in Hooks?
// passing the empty array([])
useEffect(() => {
// simulate componentDidMount lifecycle
return () => {
// simulate componentWillUnmount lifecycle
};
}, []);
Problem: The props and state inside the effect will always have their initial values when passing an empty array([]).
Take a look at the example below.
The Demo Component
can be shown or hidden, so we can make this component mount
or unmount
.
function App() {
const [demoVisible, setDemoVisible] = useState(true);
return (
<div>
<h2>Demo showcase</h2>
<button
onClick={() => {
setDemoVisible((prev) => !prev);
}}
>
toggle ComponentVisible
</button>
{demoVisible && <Demo />}
</div>
);
}
There is a increment button
to change the count
state.
Click the increment button
several times and hide the Demo Component
via clicking the toggle button
to trigger the cleanup function in useEffect
.
The count
value is also the initial value 0
.
function Demo() {
const [count, setCount] = useState(0);
const onClick = () => {
setCount((prev) => prev + 1);
};
useEffect(() => {
console.log(`count in effect: ${count}`); // initial value 0
return () => {
console.log(`count in effect cleaner: ${count}`); // initial value 0
};
}, []);
return (
<div>
<button onClick={onClick}>increment</button>
<div>count value: {count} </div>
</div>
);
}
Why? Because of the Closure
. The callback function of useEffect refers to count
variable through the Closure
. It doesn't change no matter how many times re-render occurs. It is always the value of the first render.
Solution: How to get the latest state in useEffect when passing an empty array([]) ?
Solution 1: Add count to the useEffect dependency list.
function DependencySolution() {
// ...
useEffect(() => {
console.log(`count in effect of DependencySolution: ${count}`);
return () => {
console.log(`count in effect cleaner of DependencySolution: ${count}`);
};
}, [count]);
// ...
}
When we hide the component, we can the the latest count in useEffect
cleanup callback.
But this way is not very friendly, if the callbacks in useEffect
are about event. Like the below code.
useEffect(() => {
const onResize = () => {};
window.addEventListener('resize', onResize);
return(() => {
window.removeEventListener('resize', onResize);
});
}, [count]);
This would hardly be efficient. It's a bad way to frequently add and remove the event listener.
Solution 2: useRef
We can use useRef
to store the latest value of the state.
The useRef
returned object will persist for the full lifetime of the component.countRef
doesn't change, what changes is its current
properties
function RefSolution() {
// ...
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
useEffect(() => {
console.log(`count in effect of RefSolution: ${countRef.current}`);
return () => {
console.log(
`count in effect cleaner of RefSolution: ${countRef.current}`
);
};
}, []);
// ...
}
In this example, we are using a state
in Component, but the same problem and solutions could also be applied to props
.
You can try the code on stackblitz code edit
0 Comments