Using addEventListener() with React useReducer() returns multiple keypresses

I am making a calculator with React and wanted to add the functionality for the user to input numbers directly from their keyboard. I used window.addEventListner() with useEffect() but when ever I press a key one the keyboard I get multiple inputs.

Lets say I pressed “1” once on my keyboard, then I will get “11111111” on the screen and printed to the console.

Here is the code I am using:

import { ACTIONS } from "./App";
import { useEffect } from "react";

export default function DigitButton({ dispatch, digit }) {
  useEffect(() => {
    window.addEventListener('keypress', (event) => {
      dispatch({
        type: ACTIONS.ADD_DIGIT,
        payload: {
          digit: event.key
        }
      })
    });
  });

  return <button
      onClick={() => {
        dispatch({
          type: ACTIONS.ADD_DIGIT,
          payload: { digit }
        })
      }}
    >
      {digit}
    </button>
}

  • Your event is registering multiple times. It should only be called once, and you should also remove this event listener when this component unloads. To achieve this, start by passing an empty array ([]) as the second parameter of the useEffect function. Within this useEffect, return a method that removes the ‘keypress’ event listener.

    – 

You create/add an event listener each time DigitButton renders, and never clean any of them up when the component unmounts. Add an empty dependency array to the useEffect so the effect runs exactly once, and return a cleanup function to remove the event listener.

Example:

export default function DigitButton({ dispatch, digit }) {
  useEffect(() => {
    const handler = (event) => {
      dispatch({
        type: ACTIONS.ADD_DIGIT,
        payload: { digit: event.key }
      });
    };

    window.addEventListener('keypress', handler);

    // return cleanup function to remove listener on unmount
    return () => {
      window.removeEventListener('keypress', handler);
    };
  }, []); // <-- empty dependency, run effect once

  return (
    <button
      onClick={() => {
        dispatch({
          type: ACTIONS.ADD_DIGIT,
          payload: { digit }
        });
      }}
    >
      {digit}
    </button>
  );
}

Leave a Comment