最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React keypress event taking only initial state values and not updated values - Stack Overflow

programmeradmin0浏览0评论

I am creating a React application in which I have one state called input which is taking input from user. I want that when I press enter key, alert should display the input which is getting set in state. However, on clicking enter key, only default value set in input state is getting displayed.

But when I click on a button (which I have created to display the input in alert), then the input displayed in alert is correct.

Please see the code snippet below for reference:

import React, { useEffect, useRef, useState } from 'react';

export default function ShowAlertExample() {

  const [input, setInput ] = useState('1');
  const handleInputChange = (e) => {
    setInput(e.target.value);
  }

  const handleShowAlert = () => {
    alert(input);
  }

  const checkKeyPress = (e) =>{
    const { key, keyCode } = e;
    console.log(key, keyCode)
    if (keyCode === 13 ) {
      alert(input);
    }
  }

  useEffect(()=>{
    window.addEventListener("keydown", checkKeyPress)
    return ()=>{
      window.removeEventListener("keydown", checkKeyPress)
    }
  }, [])
  
  return (
    <div>
      <header className="App-header">
        <input value={input} onChange={handleInputChange} />
       <button onClick={handleShowAlert}>Show Alert</button>
      </header>
    </div>
  );
}

I made one change which can be seen in the code snippet below. I removed the [] from dependency list of useEffect() and it started working fine, but now the issue is it is adding and removing event listener each time I add something to input. This is not a good practice I guess if we are creating a big application.

import React, { useEffect, useRef, useState } from 'react';

export default function ShowAlertExample() {

  const [input, setInput ] = useState('1');
  const handleInputChange = (e) => {
    setInput(e.target.value);
  }

  const handleShowAlert = () => {
    alert(input);
  }

  const checkKeyPress = (e) =>{
    const { key, keyCode } = e;
    console.log(key, keyCode)
    if (keyCode === 13 ) {
      alert(input);
    }
  }

  useEffect(()=>{
    window.addEventListener("keydown", checkKeyPress)
    console.log("event listener added")
    return ()=>{
      console.log("event listener removed")
      window.removeEventListener("keydown", checkKeyPress)
    }
  })
  
  return (
    <div>
      <header className="App-header">
        <input value={input} onChange={handleInputChange} />
       <button onClick={handleShowAlert}>Show Alert</button>
      </header>
    </div>
  );
}

Is there anyway I can add event listener with the desired functionality without causing any performance issue. That is when I press Enter key, the alert should display the correct input. I have referred to the below Stack Overflow post and tried using useCallback() but it didn't work out.

:~:text=hooks%20to%20below-,useEffect(()%20%3D%3E%20%7B%20window.,the%20effect%20once%2C%20imitating%20componentDidMount%20.

I am creating a React application in which I have one state called input which is taking input from user. I want that when I press enter key, alert should display the input which is getting set in state. However, on clicking enter key, only default value set in input state is getting displayed.

But when I click on a button (which I have created to display the input in alert), then the input displayed in alert is correct.

Please see the code snippet below for reference:

import React, { useEffect, useRef, useState } from 'react';

export default function ShowAlertExample() {

  const [input, setInput ] = useState('1');
  const handleInputChange = (e) => {
    setInput(e.target.value);
  }

  const handleShowAlert = () => {
    alert(input);
  }

  const checkKeyPress = (e) =>{
    const { key, keyCode } = e;
    console.log(key, keyCode)
    if (keyCode === 13 ) {
      alert(input);
    }
  }

  useEffect(()=>{
    window.addEventListener("keydown", checkKeyPress)
    return ()=>{
      window.removeEventListener("keydown", checkKeyPress)
    }
  }, [])
  
  return (
    <div>
      <header className="App-header">
        <input value={input} onChange={handleInputChange} />
       <button onClick={handleShowAlert}>Show Alert</button>
      </header>
    </div>
  );
}

I made one change which can be seen in the code snippet below. I removed the [] from dependency list of useEffect() and it started working fine, but now the issue is it is adding and removing event listener each time I add something to input. This is not a good practice I guess if we are creating a big application.

import React, { useEffect, useRef, useState } from 'react';

export default function ShowAlertExample() {

  const [input, setInput ] = useState('1');
  const handleInputChange = (e) => {
    setInput(e.target.value);
  }

  const handleShowAlert = () => {
    alert(input);
  }

  const checkKeyPress = (e) =>{
    const { key, keyCode } = e;
    console.log(key, keyCode)
    if (keyCode === 13 ) {
      alert(input);
    }
  }

  useEffect(()=>{
    window.addEventListener("keydown", checkKeyPress)
    console.log("event listener added")
    return ()=>{
      console.log("event listener removed")
      window.removeEventListener("keydown", checkKeyPress)
    }
  })
  
  return (
    <div>
      <header className="App-header">
        <input value={input} onChange={handleInputChange} />
       <button onClick={handleShowAlert}>Show Alert</button>
      </header>
    </div>
  );
}

Is there anyway I can add event listener with the desired functionality without causing any performance issue. That is when I press Enter key, the alert should display the correct input. I have referred to the below Stack Overflow post and tried using useCallback() but it didn't work out.

https://stackoverflow.com/questions/55565444/how-to-register-event-with-useeffect-hooks#:~:text=hooks%20to%20below-,useEffect(()%20%3D%3E%20%7B%20window.,the%20effect%20once%2C%20imitating%20componentDidMount%20.

Share Improve this question edited Feb 15, 2021 at 19:37 Brian Tompsett - 汤莱恩 5,88372 gold badges61 silver badges133 bronze badges asked Feb 15, 2021 at 18:44 satyamsatyam 811 gold badge1 silver badge4 bronze badges 5
  • 1 What is the purpose of checkKeyPress function? Perhaps if you explained your use-case we could suggest a more optimal solution. I.e. It is usually better to just attach an onKeyDown handler to the inputs that care about that event. This removes the stale enclosure issue. See codesandbox demo – Drew Reese Commented Feb 15, 2021 at 19:22
  • Hi @DrewReese - Thanks for your comment . My use case : I am making an application where I have provided keyboard shortcuts . When user clicks 'O' , a dialog box opens where there are radio buttons, checkboxes and text fields (pre-populated using some initial state) and a submit button . I want that when user presses enter key , I can use the data set in the states and form a JSON packet and submit them in POST request . Thats why I want to add checkKeyPress function to the event listener in useEffect() so that in checkKeyPress() , I can call submit() request . – satyam Commented Feb 15, 2021 at 19:39
  • 1 Yes, you mention submitting a request and I agree with @Lakshya that perhaps a better solution would be to use a semantic form with onSubmit handler to access the form fields. If you store the form field values in component state then you'll always have the issue of needing to re-enclose current state values in any callbacks. The alternative to state is to use a React ref per input/field and access the current value when you need to. – Drew Reese Commented Feb 15, 2021 at 19:52
  • Drew and Lakshya - Thanks to both for quick replies . One more question . My second code snippet which I posted above works fine . Only reason I wasn't using it was because it was adding and removing checkKeyPress to the event handler on every key press . But as @Lakshya told, we need to bind and unbind event handler after each state update . Can you tell if I can go ahead with this code as I don't need to add any dependency list here or add event handler directly to element . Or is it not a good practice to go with this solution (refer to my second code snippet above) . – satyam Commented Feb 15, 2021 at 20:05
  • 1 The useEffect without dependency will remove and re-add the event listener and callback on every render of the component, versus only when the dependency updates. While it may not be an issue with resetting the handler it's still unnecessary to do so on every render. – Drew Reese Commented Feb 15, 2021 at 20:07
Add a comment  | 

2 Answers 2

Reset to default 14

Since you always require the latest state value of input, you will have to add checkKeyPress to your useEffect's dependency list and checkKeyPress should itself have input in the wrapper useCallback deps list.

 const checkKeyPress = useCallback((e) => {
    const { key, keyCode } = e;
    console.log(key, keyCode);
    if (keyCode === 13) {
      alert(input);
    }
  },[input]);

  useEffect(() => {
    window.addEventListener("keydown", checkKeyPress);
    return () => {
      window.removeEventListener("keydown", checkKeyPress);
    };
  }, [checkKeyPress]);

In your older code ,an empty array in useEffect means that you're adding an event listener to the checkKeyPress that was available after first render. Now that checkKeyPress closes over the default value of input.

You need to ensure that every time that input updates, latest checkKeyPress (one which closes over the latest input) is added as an event handler and the old one removed.

Issue

You've enclosed the initial input state value in the checkKeyPress callback for the "keydown" event. Each time the callback is invoked you are accessing a stale state enclosure.

Solution

Instead of trying to use a global keyDown handler just attach an event handler directly to the elements you want to a callback to respond to, which is the standard way to handle DOM interactions in React anyway. This removes the stale enclosure and makes it a standard callback.

function ShowAlertExample() {
  const [input, setInput] = useState("1");
  const handleInputChange = (e) => {
    setInput(e.target.value);
  };

  const handleShowAlert = () => {
    alert(input);
  };

  const checkKeyPress = (e) => {
    const { key, keyCode } = e;
    console.log(key, keyCode);
    if (keyCode === 13) {
      alert(input);
    }
  };

  return (
    <div>
      <header className="App-header">
        <input
          value={input}
          onChange={handleInputChange}
          onKeyDown={checkKeyPress} // <-- attach event handler
        />
        <button onClick={handleShowAlert}>Show Alert</button>
      </header>
    </div>
  );
}

发布评论

评论列表(0)

  1. 暂无评论