0

I'm trying to manipulate the DOM based on what's being typed (text) in an input in a React component:

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";

function App() {
  const [text, setText] = useState("Initial value");

  function handleChange(event) {
    setText(event.target.value);
  }

  useEffect(() => {
    function handleKeypress(event) {
      console.log("text", text);
      // do something with the DOM with text
    }

    document.addEventListener("keydown", handleKeypress);

    return () => {
      document.removeEventListener("keydown", handleKeypress);
    };
  }, [text]);

  return (
    <div id="keys">
      <input type="text" onChange={handleChange} value={text} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

It works, but something strange happens. text won't change unless keydown is triggered a second time.

For example, if you press a, console.log("text", text) will log Initial value. It won't log a. I think there's some kind of delay.

Why is this? And how to change the code so that console.log("text", text) logs the key that's being pressed?

Live code: https://codesandbox.io/s/react-hooks-useeffect-forked-sfujxi?file=/src/index.js

Note: The reason I'm using the event listener is that I want the code to also run when, for example, I press Ctrl + Key (in that situation text doesn't change).

alexchenco
  • 53,565
  • 76
  • 241
  • 413
  • 2
    Why did you expect anything different? The listener is triggered by the key press, which necessarily happens _before_ the new value is set, the component rerenders and the new event listener (closed over the new value) is created and attached. For that matter, why are you using the event listener at all? Just use the text state you've already defined, or https://reactjs.org/docs/refs-and-the-dom.html if you really need access to the element. – jonrsharpe Dec 17 '22 at 08:27
  • Does this answer your question? [useState in useEffect does not update state](https://stackoverflow.com/questions/64191896/usestate-in-useeffect-does-not-update-state) – Amit Chauhan Dec 17 '22 at 08:30
  • 1
    Note this is closely related to https://stackoverflow.com/q/54069253/3001761 - your event listener likewise gets the value from the closure. – jonrsharpe Dec 17 '22 at 08:31

2 Answers2

1

It seems that this is because the text state is updated in the next render of the component, but the event block is still using the old value before the update.

Try not to use a event listener but let useEffect capture the change of the text state and it should log the correct input (when text changes):

  useEffect(() => {
    console.log("text", text);
  }, [text]);

Alternatively if the goal is to log the key pressed, perhaps consider to just capture it with event since the event will have the value of the pressed key.

There is no need to read from text state for this approach, instead perhaps consider to set the captured value as a state for display in some other components.

  useEffect(() => {
    function handleKeypress(event) {
      console.log(event.keyCode);
    }
    document.addEventListener("keydown", handleKeypress);
    return () => {
      document.removeEventListener("keydown", handleKeypress);
    };
  }, []);
John Li
  • 6,976
  • 3
  • 3
  • 27
  • Oh, the reason I'm using the event listener is that I want the code to also run when, for example, I press `Ctrl + Key` (in that situation `text` doesn't change). – alexchenco Dec 17 '22 at 08:32
  • 1
    @alexchenco Indeed in such case the `onChange` of `input` will not run, perhaps consider to track the keys pressed separately with events if needed, but not necessarily dependent on the `text` state. – John Li Dec 17 '22 at 08:50
  • 1
    If I use `event.key` instead of `text`, I'll have to recreate the behavior of the `input`. For example, exclude arrows, enter, move the cursor from left to right etc. I think give it some more thought, but you answered my question. Thanks. – alexchenco Dec 17 '22 at 09:52
1

Considering the Note that you have provided in your question If you want the handleKeypress function to run when the user presses a key combination, such as Ctrl + Key, you can use the event.ctrlKey property to check if the Ctrl key is being pressed.

function handleKeypress(event) {
  if (event.ctrlKey) {
    console.log("Ctrl + Key was pressed");
  }
}

If you want to check for specific key combinations for example, to check for Ctrl + S you can do that using event.key property.

function handleKeypress(event) {
  if (event.ctrlKey && event.key === "s") {
    console.log("Ctrl + S was pressed");
  }
}
Sachin
  • 149
  • 1
  • 16