skip to Main Content
import { useState, useRef, useEffect } from "react";
    
function App() {
  const [message, setMessage] = useState("");
  const inpt = useRef();

  const handleKeyDown = (e) => {
    if (e.key === "Enter") {
      handleClick();
    }
  };

  useEffect(() => {
    inpt.current.addEventListener("keydown", handleKeyDown);
    return () => inpt.current.removeEventListener("keydown", handleKeyDown);
  }, []);

  function handleClick() {
    console.error(message); 
  }
  return (
    <>
      <div>
        <input ref={inpt} type="text" name="" id="" value={message} onChange={(e)=> setMessage(e.target.value)} />
        <button onClick={handleClick}>click</button>
      </div>
    </>
  );
}

export default App;

When I click on the button I can log message correctly, but when I press the "Enter" key message logs as the initial value "".

Can you help me understand why this happening and what I should do?

2

Answers


  1. Since you are using empty dependency array in useEffect hook, upon re-renders due to the state changes, its cleanup function wont get executed and ref will keep pointing to stale input element.
    To avoid this, you should add message of handleKeydown as dependency.

    useEffect(() => {
        inpt.current.addEventListener("keydown", handleKeyDown);
        return () => inpt.current.removeEventListener("keydown", handleKeyDown);
      }, [message]);
    
    Login or Signup to reply.
  2. You need to make the message a dependency of the effect, as Ravi stated.

    const { useEffect, useRef, useState } = React;
        
    function App() {
      const [message, setMessage] = useState("");
      const inputRef = useRef();
    
      const handleKeyDown = (e) => {
        if (e.key === "Enter") {
          handleClick();
        }
      };
    
      useEffect(() => {
        inputRef.current.addEventListener("keydown", handleKeyDown);
        return () => inputRef.current.removeEventListener("keydown", handleKeyDown);
      }, [message]);
    
      function handleClick() {
        console.error(`MESSAGE: ${message}`); 
      }
      return (
        <React.Fragment>
          <div>
            <input ref={inputRef} type="text" value={message} onChange={(e)=> setMessage(e.target.value)} />
            <button onClick={handleClick}>Click</button>
          </div>
        </React.Fragment>
      );
    }
    
    ReactDOM
      .createRoot(document.getElementById("root"))
      .render(<App />);
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

    Use a form

    I better way to do this would to use a form. It has built-in Enter key support.

    If you are using a ref on the field, you do not need to store a state!

    const { useCallback, useRef } = React;
        
    function App() {
      const inputRef = useRef();
    
      const handleSubmit = useCallback((e) => {
        e.preventDefault(); // Do not navigate away from current page
        console.error(`MESSAGE: ${inputRef.current.value}`); 
      }, [inputRef]);
      
      return (
        <React.Fragment>
          <form onSubmit={handleSubmit}>
            <input ref={inputRef} type="text" name="message" />
            <button type="submit">Submit</button>
          </form>
        </React.Fragment>
      );
    }
    
    ReactDOM
      .createRoot(document.getElementById("root"))
      .render(<App />);
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search