Emil
Emil

Reputation: 107

State won't change in "keydown" event in React

I am making a an app where I need to register the keys the user presses. To do that I am using keydown and keyup events. However, I don't want to register the same key multiple times when the user holds it down.

I want to make it so the downHandler function won't run multiple times whenever you hold down a key. I only want it to run once. I try to use an if statement for that and setting keyPressed to true so it won't run again. I try to log keyPressed right after I set it to true, but it logs as false, and the function still runs again.

I tried to remove setKeyPressed(false) in the downHandler function, but keyPressed was still false no matter what.

Here is my app.js:

import { useState, useEffect } from "react";


function App() {
  const key = useKeyPress();

  function useKeyPress(targetKey) {

    const [keyPressed, setKeyPressed] = useState(false);

    function downHandler(key) {
      if (keyPressed === false) {
        setKeyPressed(true);
        console.log(keyPressed)
      }
    }

    const upHandler = (key) => {
      console.log("up")
      setKeyPressed(false);
    };

    useEffect(() => {
      window.addEventListener("keydown", downHandler);
      window.addEventListener("keyup", upHandler);

      return () => {
        window.removeEventListener("keydown", downHandler);
        window.removeEventListener("keyup", upHandler);
      };
    }, []);
    return keyPressed;
  }

  return (
    <div>

    </div>
  )
}

export default App;

Upvotes: 0

Views: 370

Answers (1)

Dennis Vash
Dennis Vash

Reputation: 53874

The custom hook should be created in outer scope, when you writing it in the render function (function component body) you are recreating the hook on every render. Hence you losing state each time.

Surprised that you didn't get any lint warning of breaking the rules of hook - writing a hook inside a function, make sure its configured.

Moreover, you are making comparison with stale state keyPressed === false duo to closures, you can fix it by using a reference.

function useKeyPress(targetKey) {
  const [keyPressed, setKeyPressed] = useState(false);

  const keyPressedRef = useRef();

  useEffect(() => {
    keyPressedRef.current = keyPressed;
    console.log("keyPressed", keyPressed);
  }, [keyPressed]);

  useEffect(() => {
    function downHandler(key) {
      if (keyPressedRef.current === false) {
        setKeyPressed(true);
      }
    }

    const upHandler = (key) => {
      setKeyPressed(false);
    };

    window.addEventListener("keydown", downHandler);
    window.addEventListener("keyup", upHandler);

    return () => {
      window.removeEventListener("keydown", downHandler);
      window.removeEventListener("keyup", upHandler);
    };
  }, []);

  return keyPressed;
}

export default function App() {
  const key = useKeyPress();

  return <div></div>;
}

https://codesandbox.io/s/inspiring-jasper-3lym7p?file=/src/App.js:77-897

Upvotes: 2

Related Questions