React: `useEffect` doesn't work for `keydown` event listener

Created on 4 Jun 2019  路  3Comments  路  Source: facebook/react

I have a stateless component which needs to listen on keyboard event. It adds keydown listener when the component is mounted and remove it when the component is unmounted. There is a state test is boolean value. It is set to true when the component is mounted. But in the keydown event listener, its value always false. It looks like the listener doesn't take the state reference. What's wrong with my code?

const { useEffect, useState } = React;


const Comp = () => {
  const [test, setTest] = useState(false);
  const keyPressHandler = (e) => {
    setTest(!test);
    console.log(test);
  }
  useEffect(() => {
    setTest(true);
    window.addEventListener('keydown', keyPressHandler);
    return () => {
      window.removeEventListener('keydown', keyPressHandler);
    };
  }, []);

  return (
    <div className="test">
      hello {test + ""}
    </div>
  );
};

A live example can be found at: https://codepen.io/zhaoyi0113/pen/mYozVp

Most helpful comment

If you remove this ,[] second argument from the useEffect hook in your code, it'll start working. The empty array tells it to run only in the very beginning and end of the component lifecycle (similar to componentDidMount & componentWillUnMount). And, your keyPressHandler was added as a listener only in the first render thus retaining the value of text as "". If you want to use the updated values of text, you should make the suggested change.

Here's a fiddle that works just fine: https://codepen.io/anon/pen/oRVOgK

All 3 comments

If you remove this ,[] second argument from the useEffect hook in your code, it'll start working. The empty array tells it to run only in the very beginning and end of the component lifecycle (similar to componentDidMount & componentWillUnMount). And, your keyPressHandler was added as a listener only in the first render thus retaining the value of text as "". If you want to use the updated values of text, you should make the suggested change.

Here's a fiddle that works just fine: https://codepen.io/anon/pen/oRVOgK

Also you can put keyPressHandler function inside useEffect body and use setTest to getting previous state not from closure, but from second form with callback.

  const [text, setText] = useState('');

  useEffect(() => {
    const keyPressHandler = (e) => {
      setText((text) => text + e.key);
    };

    document.addEventListener('keydown', keyPressHandler);
    return () => {
      document.removeEventListener('keydown', keyPressHandler);
    };
  }, []);

That makes sense, thanks.

Was this page helpful?
0 / 5 - 0 ratings