How do I window removeEventListener using React useEffect

百般思念 提交于 2020-05-28 12:05:06

问题


In React Hooks documents it is shown how to removeEventListener during the component's cleanup phase. https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect

In my use case, I am trying to removeEventListener conditional to a state property of the functional component.

Here's an example where the component is never unmounted but the event listener should be removed:

function App () {
  const [collapsed, setCollapsed] = React.useState(true);
  React.useEffect(
    () => {
      if (collapsed) {
        window.removeEventListener('keyup', handleKeyUp); // Not the same "handleKeyUp" :(
      } else {
        window.addEventListener('keyup', handleKeyUp);
      }
    },
    [collapsed]
  );
  function handleKeyUp(event) {
    console.log(event.key);
    switch (event.key) {
      case 'Escape':
        setCollapsed(true);
        break;
    }
  }

  return collapsed ? (
    <a href="javascript:;" onClick={()=>setCollapsed(false)}>Search</a>
  ) : (
    <span>
      <input placeholder="Search" autoFocus />&nbsp;
      <a href="javascript:;">This</a>&nbsp;
      <a href="javascript:;">That</a>&nbsp;
      <input placeholder="Refinement" />
    </span>
  );
}
ReactDOM.render(<App />, document.body.appendChild(document.createElement('div')));

(Live sample at https://codepen.io/caqu/pen/xBeBMN)

The problem I see is that the handleKeyUp reference inside removeEventListener is changing every time the component renders. The function handleKeyUp needs a reference to setCollapsed so it must be enclosed by App. Moving handleKeyUp inside useEffect also seems to fire multiple times and lose the reference to the original handleKeyUp.

How can I conditionally window.removeEventListener using React Hooks without unmounting the component?


回答1:


You can put the handleKeyUp function inside of the function given to useEffect and only add the listener and return a cleanup function when collapsed is false.

useEffect(() => {
  if (!collapsed) {
    function handleKeyUp(event) {
      switch (event.key) {
        case "Escape":
          setCollapsed(true);
          break;
      }
    }

    window.addEventListener("keyup", handleKeyUp);
    return () => window.removeEventListener("keyup", handleKeyUp);
  }
}, [collapsed]);



回答2:


Tholle's answer may work, but it's bad practise to declare a function inside an if. It makes it harder to follow when the function is declared and when it is not. Also it can lead to bugs because functions are hoisted up.
There's a neater way to fix it:

By wrapping your event handler with the useCallback hook.

const [collapsed, setCollapsed] = useState(true)

const handleKeyUp = useCallback(() => {
    switch (event.key) {
        case "Escape":
            setCollapsed(true)
            break
    }
}, [setCollapsed])

useEffect(() => {
    window.addEventListener("keyup", handleKeyUp)
    // See note below
    return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed])

If you use a lot of event handlers in useEffect, there's a custom hook for that: https://usehooks.com/useEventListener/

Note:

Since you are listening to keyup, you might even want to remove the event listener when collapsed === true, because there's nothing to collapse. Otherwise events will keep firing as long as the component is mounted.

useEffect(() => {
    if (!collapsed) {
        window.addEventListener("keyup", handleKeyUp)
    } else {
        window.removeEventListener("keyup", handleKeyUp)
    }

    return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed]);


来源:https://stackoverflow.com/questions/55360736/how-do-i-window-removeeventlistener-using-react-useeffect

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!