Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

React useEffect adds an event listener to ref on every input change

Take the following component:

function MyComponent() {
    const [ inputValue, setInputValue ] = useState( '' );
    const [ submittedValue, setSubmittedValue ] = useState( '' );
    const inputRef = useRef<HTMLInputElement>( null );

    useEffect( () => {
        const inputElement = inputRef.current;
        const handleEvent = ( event ) => {
            if ( event.key === 'Enter' ) {
                event.preventDefault();
                setSubmittedValue( inputValue );
            }
        };
        console.log( 'adding listener' );
        inputElement?.addEventListener( 'keydown', handleEvent );
        return () => inputElement?.removeEventListener( 'keydown', handleEvent );
    }, [ inputValue ] );

    return (
        <>
            <input value={inputValue} onChange={( e ) => setInputValue( e.target.value )} ref={inputRef}></input>
            <p>{submittedValue}</p>
        </>
    );
}

The component lets you type in a controlled input, and hit the ‘Enter’ key to "submit" the current input value. The submitted value should display below the input and remain static until "submitting" again.

This works as intended. The problem is that a ‘keydown’ listener is added on every keystroke.

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

Expected results: ‘adding listener’ prints once on mount
Actual results: ‘adding listener’ prints on every keystroke

I understand why. On every keystroke, onChange runs, which runs setInputValue, which changes useEffect‘s dependency list, which adds the listener again.

The easiest way to solve this problem is to just add onKeyDown={handleEvent} to the input. But that would be too easy.

I only got to this point because the legacy Input element I’m using does not implement onKeyDown

Is there a way to achieve the expected results without using onKeyDown?

>Solution :

Since you’re also removing an event listener on every keystroke, I think what you’re doing now is perfectly fine – the handler is only being fired once per event, as desired, and the handler will be removed when the component unmounts, which is good. Listeners being added and removed frequently might not feel great, but it honestly doesn’t cause any issues in 99% of situations, and it works, so probably isn’t worth worrying about.

If you really don’t want to add the listener multiple times, I suppose you could retrieve the value from the ref instead of requiring an up-to-date closure over the inputValue.

function MyComponent() {
    const [ inputValue, setInputValue ] = React.useState( '' );
    const [ submittedValue, setSubmittedValue ] = React.useState( '' );
    const inputRef = React.useRef( null );

    React.useEffect( () => {
        const inputElement = inputRef.current;
        const handleEvent = ( event ) => {
            if ( event.key === 'Enter' ) {
                event.preventDefault();
                setSubmittedValue( inputRef.current.value );
            }
        };
        inputElement.addEventListener( 'keydown', handleEvent );
        return () => inputElement.removeEventListener( 'keydown', handleEvent );
    }, [] );

    return (
        <React.Fragment>
            <input value={inputValue} onChange={( e ) => setInputValue( e.target.value )} ref={inputRef}></input>
            <p>{submittedValue}</p>
        </React.Fragment>
    );
}

ReactDOM.render(<MyComponent />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>
Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading