DEV Community

Discussion on: Why pressing the key works only once in my React project?

Collapse
 
merri profile image
Vesa Piittinen • Edited

The code within useEffect(() => {}, []) is executed only once. The value for itWorks upon creation of the function is true and consequently it will always be true.

There are two ways you can fix this.

Method 1: update listener each time it changes.

import { useState, useEffect } from 'react'

const HandleKeypress = () => {

    const [itWorks, setItWorks] = useState(true)

    useEffect(() => {
        // reference must be same for addEventListener and removeEventListener
        // = can't use inline arrow function!
        function listener(event) {
            if (event.code === 'Space') setItWorks(!itWorks)
        }
        document.addEventListener('keypress', listener)
        return () => {
            document.removeEventListener('keypress', listener)
        }
    }, [itWorks])

    return (
        <div>
            <p>{itWorks ? 'It works!' : 'It does not'}</p>
            <button 
                onClick={() => setItWorks(!itWorks)}
            >Press me</button>
        </div>
    )
}

export default HandleKeypress
Enter fullscreen mode Exit fullscreen mode

This works because now each time that itWorks changes the listener is updated.

Method 2: use setState with a function

import { useState, useEffect } from 'react'

const HandleKeypress = () => {

    const [itWorks, setItWorks] = useState(true)

    useEffect(() => {
        document.addEventListener('keypress', (e) => {
            if (e.code === 'Space') setItWorks(state => !state)
        })
    }, [])

    return (
        <div>
            <p>{itWorks ? 'It works!' : 'It does not'}</p>
            <button 
                onClick={() => setItWorks(!itWorks)}
            >Press me</button>
        </div>
    )
}

export default HandleKeypress
Enter fullscreen mode Exit fullscreen mode

The function gets the current value in the state so if you're only toggling it you can simply reverse it. This works for all cases where you determine the next state based on the previous state.

Collapse
 
snorkypie profile image
Steeve Lennmark

You still want to remove the event listener on unmount in the second example though!

Collapse
 
ptifur profile image
Yar

Thanks for the insights! With these corrections both methods work indeed!

Is there a reason to prefer one over the other?

Collapse
 
merri profile image
Vesa Piittinen

The second one causes slightly less work for CPU to do, although the effect is quite minimal with today's computing power. And the work only happens when the value changes, in this case from user action, so not really a big argument for it.

However it is always a good idea to clean up what you create to the DOM, so in this case by adding the removeEventListener part even if using the second approach.

Thread Thread
 
ptifur profile image
Yar

Thank you! Makes sense.