Home > Software design >  React/Next.js Keydown Event Listener Not Triggering Correctly
React/Next.js Keydown Event Listener Not Triggering Correctly

Time:08-08

I'm trying to build an Image Gallery Lightroom component within React/Next.js, and I've got everything working so far in that clicking on the gallery images opens a modal in a react portal. I've got a close button and left/right arrow buttons to navigate through the images.

The gallery has a set of six images, with an array of images being sent to the main gallery component.

[
{src: "/bear.jpg", width: '600', height: '750', alt: 'bear'},
{src: "/duck.jpg", width: '600', height: '750', alt: 'duck'},
{src: "/wombat.jpg", width: '600', height: '750', alt: 'wombat'},
{src: "/salamander.jpg", width: '600', height: '750', alt: 'salamander'},
{src: "/goose.jpg", width: '600', height: '750', alt: 'goose'},
{src: "/mouse.jpg", width: '600', height: '750', alt: 'mouse'}
]

I have a useState component that tracks 'num' value that will line up with the value of the array (0-5 in this case). My next and last arrow buttons will move to either the previous or next image in the list and cycle around if they're at the start/end to rerender the image. This is all working fine.

Where the problem starts is that I wanted to add event listeners to allow the ESC key to close the gallery (also working fine), and arrow keys to navigate left/right through the gallery similar to the buttons.

I added console.logs to test that the functions are triggering, but it keeps triggering on the same values (4 for left and 5 for right). I've been digging around but for the life of me I can't figure out what I'm missing. Is there a special way that document event listeners interact with react? Or is there maybe a better way to trigger these functions? I appreciate any assistance, here's the code I'm working with:

Functions/State (imgNumber and setImgNumber are useState props passed from the main component)

  useEffect(() => {
    console.log(gallery);
    document.addEventListener("keydown", keyDownHandler, false);

    return () => {
      document.removeEventListener("keydown", keyDownHandler, false);
    };
  }, []);

  function keyDownHandler(e) {
    if (e.repeat) return;

    console.log("Key Pressed: ", e.key);

    if (e.key === "Escape") {
      e.preventDefault();
      closeModal();
    }

    if (e.key === "ArrowLeft") {
      e.preventDefault();
      navLeftHandle();
    }

    if (e.key === "ArrowRight") {
      e.preventDefault();
      navRightHandle();
    }
  }

  function closeModal() {
    setViewModal(false);
  }

  function navLeftHandle() {
    if (imgNumber <= 0) {
      setImgNumber(gallery.length - 1);
      console.log("New Image Number: ", imgNumber);
      return;
    }

    setImgNumber(imgNumber - 1);
    console.log("New Image Number: ", imgNumber);
  }
  function navRightHandle() {
    if (imgNumber >= gallery.length - 1) {
      setImgNumber(0);
      return;
    }

    setImgNumber(imgNumber   1);
  }

React JSX Section

  return (
    <div className="post__modal">
      <div className="post__modal--content">
        <div className="post__modal--image">
          <Image
            src={gallery[imgNumber].src}
            alt={gallery[imgNumber].alt}
            width={gallery[imgNumber].width}
            height={gallery[imgNumber].height}
          />
          <button className="post__modal--image--button" onClick={closeModal}>
            <ImageClose />
          </button>
        </div>
        {caption && (
          <p className="post__modal--caption">{contentEval(caption)}</p>
        )}
      </div>
      <div className="post__modal--navigation">
        <button
          className="post__modal--navigation-left"
          onClick={navLeftHandle}
        >
          <LeftChevron />
        </button>
        <button
          className="post__modal--navigation-right"
          onClick={navRightHandle}
        >
          <RightChevron />
        </button>
      </div>
      <div className="post__modal--background" onClick={closeModal} />
    </div>
  );
}

CodePudding user response:

You should use useCallback function for navRightHandle

function navRightHandle = useCallback(() => {
    if (imgNumber >= gallery.length - 1) {
      setImgNumber(0);
      return;
    }

    setImgNumber(imgNumber   1);
},[imgNumber, gallery])

CodePudding user response:

I don't know for sure which fix it was specifically, but there are a couple of problems that seemed to be conflicting. The document event listener was just not registering state changes. I was at state '3' but the event listener kept choosing '2 'for the decrease and '4' for the increase and kept referencing the initial state.

This could be because the state values were on a parent component and that might have been messing with the event listeners (even though onClick was working fine). Alternatively, it could be because I was using current state to reference the change at all. I found this answer to another similar issue:

https://stackoverflow.com/a/58439475/19712300

They created a hook they called 'useStateRef' where they set the initial useState off of an input value, set a useRef hook to mirror the state value, and then set a useEffect to update useRef, whenever the value of useState changed:

function useStateRef(initialValue) {
  const [value, setValue] = useState(initialValue);

  const ref = useRef(value);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return [value, setValue, ref];
}

I modified this custom hook with my own variable names, then plugged in 'ref.current' into my navLeft and navRight functions rather than the 'State' value. After this change, the keydown event listeners and the clicks have both been working very smoothly.

  • Related