Vinay Sharma
Vinay Sharma

Reputation: 3777

React Hooks: How to clean event listeners?

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:

What am I doing wrong here? Thanks :)

Upvotes: 1

Views: 6546

Answers (1)

Zachary Haber
Zachary Haber

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 handleClickfunction 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

Related Questions