javascriptreactjsevent-handlingkeyevent

How to add a keydown Event Listener for a react functional component


I'm trying to 'move' in my 10x10 grid by updating the activeCellId state. However none of the methods I tried works. This is my code.

const GridCells: React.FC = () => {
  const gridArray = [...Array(100).keys()];
  const color = [
    "bg-slate-50",
    "bg-slate-100",
    "bg-slate-200",
    "bg-slate-300",
    "bg-slate-400",
    "bg-slate-500",
    "bg-slate-600",
    "bg-slate-700",
    "bg-slate-800",
    "bg-slate-900",
  ];

  const [activeCellId, setActiveCellId] = useState(42);

  // useEffect(() => {
  //   document.addEventListener("keydown", updateActiveCellId, false);
  // }, []);  // this doesn't work. the activeCellId is only incremented once, and afterwards the setActiveCellId doesn't get called at all

  const updateActiveCellId = (e: React.KeyboardEvent) => {
    // will eventually be a switch case logic here, for handling arrow up, left, right down
    console.log(activeCellId);
    setActiveCellId(activeCellId + 1);
  };

  return (
    <div
      className="grid-rows-10 grid grid-cols-10 gap-0.5"
      // onKeyDown={updateActiveCellId}  this also doesn't work
    >
      {gridArray.map((value, id) => {
        const colorId = Math.floor(id / 10);
        return (
          <div
            key={id}
            className={
              "h-10 w-10 "
              + color[colorId] 
              + (id === activeCellId ? " scale-125 bg-yellow-400" : "")
            }
          >
            {id}
          </div>
        );
      })}
    </div>
  );
};

I'm trying to update a state in the react component by pressing certain keys. I've tried UseEffect with [] dep array and tried onKeyDown and it also doesn't work. I also tried following this useRef way it doesn't work too.

const innerRef = useRef(null);
  useEffect(() => {
    const div = innerRef.current;
    div.addEventListener("keydown", updateActiveCellId, false);
  }, []);  // this doesn't work at all

  const updateActiveCellId = (e: React.KeyboardEvent) => {
    console.log(activeCellId);
    setActiveCellId(activeCellId + 1);
  };

  return (
    <div
      className="grid-rows-10 grid grid-cols-10 gap-0.5"
      ref={innerRef}
    >
    ...
  )

Solution

  • Try this:

    useEffect(() => {
      document.addEventListener("keydown", updateActiveCellId, false);
      
      return () => {
        document.removeEventListener("keydown", updateActiveCellId, false);
      }
    }, [activeCellId]);
    

    The [activeCellId] is the dependency of useEffect. Everytimes activeCellId changes, the function inside useEffect will run.

    You had an empty dependency, so it ran on initial component mount only.

    The returned function containing removeEventListner is executed when the component unmounts (See cleanup function in the docs). That is to ensure you have only one event listener runnign at once.

    Documentation