How do I window removeEventListener using React useEffect

Caq*_*aqu 4 reactjs react-hooks removeeventlistener

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')));
Run Code Online (Sandbox Code Playgroud)

(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?

pub*_*orn 49

Tholle 的答案可能有效,但在if.

函数何时声明,何时未声明,这使得跟踪变得更加困难。它也可能导致错误,因为函数被提升了。

有一个更简洁的方法来解决它:

通过使用useCallback钩子包装您的事件处理程序。

const [collapsed, setCollapsed] = useState(true)

const handleKeyUp = useCallback((event) => {
    if (event.key === "Escape") {
      setCollapsed(true)
    }
}, [setCollapsed])

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

    return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed, handleKeyUp])
Run Code Online (Sandbox Code Playgroud)
  • useCallback依赖于setCollapsed. 这确保handleKeyUp在组件重新渲染时不会重新定义(状态更改时总是发生这种情况)
  • useEffect 将有条件地添加/删除事件侦听器,否则只要组件已安装,事件就会继续触发。

如果您在 useEffect 中使用了很多事件处理程序,则有一个自定义挂钩:https ://usehooks.com/useEventListener/

这是使用我的解决方案更新的问题海报示例:https : //codepen.io/publicJorn/pen/eYzwENN

  • 事实上,如果可能的话,托勒的答案更好。只要可能,最好在 useEffect 中定义回调。那么你就不需要费心使用useCallback了。正如 Kent C Dodds 建议的那样:“如果您必须为要调用的效果定义一个函数,那么请在效果回调内部执行此操作,而不是在外部。” https://epicreact.dev/myths-about-useeffect/ (9认同)

Tho*_*lle 5

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]);
Run Code Online (Sandbox Code Playgroud)