Home > Blockchain >  Keyup event listeners not using the updated states in React
Keyup event listeners not using the updated states in React

Time:08-24

I'm working on an online ide in which I have to add key shortcuts to run code, simply Ctrl Enter to execute piece of code. I am done with the adding shortcuts part, but the main issue is that whenever I use the shortcuts the states that are getting passed, are not updated, and the initial ones are used instead.

I have tried to reproduce my situation in closest way possible in the following code:

import "./styles.css";
import { useState, useEffect } from "react";

export default function App() {
  let [name, setName] = useState("John Doe");
  useEffect(() => {
    let handleShortcutKeys = (evt) => {
      if (evt.ctrlKey) {
        if (evt.key === "Enter") {
          alert(name);
        }
      }
    };
    window.addEventListener("keyup", handleShortcutKeys);
    return () => {
      window.removeEventListener("keyup", handleShortcutKeys);
    };
  }, []);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <input
        type="text"
        placeholder="enter something..."
        value={name}
        onChange={(evt) => setName(evt.target.value)}
      />
      <br />
      <br />
      <button onClick={() => alert(name)}>Alert Name</button>
    </div>
  );
}

You can play with this snippet here: https://codesandbox.io/s/event-listener-bug-mcve-u0zvff

It is a very basic code, but I don't know what is the error exactly, try to change the name and then, first try using the button Alert Name and then try to press Ctrl Enter you will be able to see the difference.

Thanks in advance!

CodePudding user response:

You can use onKeyup in input.

import "./styles.css";
import { useState} from "react";

export default function App() {
  const [name, setName] = useState("John Doe");
  const handleShortcutKeys = (evt) => {
    if (evt.ctrlKey) {
      if (evt.key === "Enter") {
        alert(name);
      }
    }
  };
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <input
        type="text"
        placeholder="enter something..."
        value={name}
        onChange={(evt) => setName(evt.target.value)}
        onKeyUp={handleShortcutKeys}
      />
      <br />
      <br />
      <button onClick={() => alert(name)}>Alert Name</button>
    </div>
  );
}

Hope this would be helpful for you.

CodePudding user response:

When you register the event listener, it gets the value of the state at that moment. and since the useEffect dependencies are empty it will run only once. so you could simple add the name dependency to the useEffect dependencies.

  useEffect(() => {
    let handleShortcutKeys = (evt) => {
      if (evt.ctrlKey) {
        if (evt.key === "Enter") {
          alert(name);
        }
      }
    };
    window.addEventListener("keyup", handleShortcutKeys);
    return () => {
      window.removeEventListener("keyup", handleShortcutKeys);
    };
  }, [name]);

alternatively you could move the event handler out of the useEffect and wrap it in an useCallback hook

  const handleShortcutKeys = useCallback((evt) => {
    if (evt.ctrlKey) {
      if (evt.key === "Enter") {
        alert(name);
      }
    }
  }, [name])
  
  useEffect(() => {
    window.addEventListener("keyup", handleShortcutKeys);
    return () => {
      window.removeEventListener("keyup", handleShortcutKeys);
    };
  }, [handleShortcutKeys]);

CodePudding user response:

When an event listener is registered, it picks the value of the state variables, that were there at the moment it was registered. That's why it is giving you the same value always. To fix it, you can either add name, in the useEffect dependency array, or use a ref, instead of state variable like this:

import "./styles.css";
import { useState, useEffect, useRef } from "react";

export default function App() {
  const nameRef = useRef("John Doe");
  useEffect(() => {
    const handleShortcutKeys = (evt) => {
      if (evt.ctrlKey) {
        if (evt.key === "Enter") {
          alert(nameRef.current);
        }
      }
    };
    window.addEventListener("keyup", handleShortcutKeys);
    return () => {
      window.removeEventListener("keyup", handleShortcutKeys);
    };
  }, []);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <input
        type="text"
        placeholder="enter something..."
        defaultValue={nameRef.current}
        onChange={(evt) => {
          nameRef.current = evt.target.value;
        }}
      />
      <br />
      <br />
      <button onClick={() => alert(nameRef.current)}>Alert Name</button>
    </div>
  );
}

Sandbox link.

  • Related