Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.1k views
in Technique[技术] by (71.8m points)

reactjs - How do I window removeEventListener using React useEffect

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Tholle's answer may work, but it's bad practice 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((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])
  • useCallback has a dependency on setCollapsed. This makes sure handleKeyUp is not redefined when the component rerenders (which always happens when state changes)
  • useEffect will conditionally add/remove the event listener, otherwise events will keep firing as long as the component is mounted.

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

Here's the question posters example updated with my solution: https://codepen.io/publicJorn/pen/eYzwENN


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...