Reputation: 3777
I have a component Example
in which I want to clear event listener when the local state display
changes to false,display
is set to true when show
prop is true and vice versa.
Code:
const Example = ({ show, onClose }) => {
const [display, setDisplay] = useState(false);
const handleClick = (e) => {
onClose();
console.log(display);
};
useEffect(() => {
if (display) {
document.addEventListener('click', handleClick);
} else {
document.removeEventListener('click', handleClick);
}
}, [display, handleClick]);
useEffect(() => {
if (show) {
setDisplay(show);
} else {
setDisplay(false);
}
}, [show]);
return <div>{display && <p>Hi</p>}</div>;
};
Issues:
handleClick()
is made every time Example
renders.handleClick()
is called, it logs display
as true.What am I doing wrong here? Thanks :)
Upvotes: 1
Views: 6546
Reputation: 11017
In order to clean up in a useEffect, return a function in the effect. That function will be called every time the effect re-runs and when the component unmounts.
The reason why this will allow for the event listener to be properly removed is because the useEffect creates a closure over the current version of the handleClick
function. Which allows the clean-up function to have the same reference so it can be properly cleaned up. It didn't work in the original because every time the useEffect re-ran, a new version of the handleClick
was closed over and then the clean-up was tried with the new version.
useEffect(() => {
if (!display) {
return;
}
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, [display, handleClick]);
You can further make the effect occur less often by using a ref to the handleClick
function instead.
For instance at it's most basic. Though you can easily abstract some of the reference and extra use-effect to a separate hook.
const handleClickRef = useRef(handleClick);
useEffect(()=>{
handleClickRef.current = handleClick;
},[handleClick])
useEffect(() => {
if (!display) {
return;
}
const funct = (evt)=>handleClickRef.current(evt);
document.addEventListener('click',funct);
return () => document.removeEventListener('click', funct);
}, [display]);
Upvotes: 5