skip to Main Content

I am experiencing a very weird bug in a basically fresh react project. I am attempting to update an array whenever the browser receives a keypress event. For some reason the update that I would expect to work is only editing the last value of the array or keeping it at a fixed length of 1.

function Component() {
  const [keypresses, setKeypresses] = useState([]);
  
  function updateKeypresses(e) {
   setKeypresses([...keypresses, e.keyCode]);
  }

  useEffect(() => {
    window.addEventListeneder('keydown', updateKeypresses);
    return () => {
      document.removeEventListener('keydown', updateKeypresses); 
    }  
  }, []);

  useEffect(() => {
    console.log(keypresses);
  }, [keypresses])
}
Output: I dont have codes memorized so image nums

press a
~> [a]
press b
~> [b]
press c
~> [c]

What I would expect:
~> [a]
~> [a,b]
~> [a,b,c]

3

Answers


  1. The reason that the keypresses array always reset it because your pointer of the updateKeypresses function isn’t changed.
    to fix it you have to add the keypresses state to the dependecies array of useEffect:

    useEffect(() => {
        window.addEventListener('keydown', updateKeypresses);
        return () => {
          document.removeEventListener('keydown', updateKeypresses);
        };
      }, [keypresses]);
    
    Login or Signup to reply.
  2. I think that you should update your state like this:

      setKeypresses(prevKeypresses => [...prevKeypresses, e.keyCode]);
    

    Because, when updating state based on the previous state, it’s recommended to use the functional form to ensure that you are working with the most up-to-date state.

    Login or Signup to reply.
  3. Function updateKeypresses is created on each render and always have latest version of keypresses. However, your effect in run only after first render, and it ‘snapshots’ version of updateKeypresses available on first render. At this moment keypresses is empty, and every time event is triggered, your code effectively executes:

    setKeypresses([...[], e.keyCode]);
    

    To fix this you can either memoize function and put it into effect dependencies:

    const [keypresses, setKeypresses] = useState([]);
      
    const updateKeypresses = useCallback((e) => {
     setKeypresses([...keypresses, e.keyCode]);
    }, []);
    
    useEffect(() => {
      window.addEventListeneder('keydown', updateKeypresses);
      return () => {
        document.removeEventListener('keydown', updateKeypresses); 
      }  
    }, [updateKeypresses]);
    

    Or, a bit simpler way it to use function with setKeypresses:

    setKeypresses((prevKeypresses) => [...prevKeypresses, e.keyCode]);
    

    This way, React will run callback function providing latest value of keypresses as parameter (prevKeypresses) and so you can update state without re-attaching event listener every time keypresses changes

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search