Diego Betto's Blog
Photo by roegger from Pixabay

June 15, 2024 · 3 min di lettura

React: event handlers and hooks

How to correctly handle event handlers in React, including events on window like resize and scroll, avoiding the most common mistakes.

Condividi:XLinkedInFacebookWhatsApp

Preface

When developing with React, events tied to mouse or keyboard interactions are usually handled directly through component attributes.

Sometimes, though, you need to handle particular events, such as those tied to the window object, like resize or scroll. So let’s look at how to handle them correctly.

Event handlers

We can write classic React event handlers. For example, if we want to handle both the click and double-click events on a div, we can write something like this:

<div onClick={handleClick} onDoubleClick={(event) => handleDblClick(event)}>
  ...
</div>

In the example above we find two event handlers specified in two different ways. Both are valid and behave similarly.

When we specify the handler in the first form, we’re saying “when the click event fires, call handleClick, passing it all the parameters the onClick attribute provides.” In the second form, instead, we’re saying to call the handleDblClick event handler passing only the event parameter.

Good practice is to primarily use component props when writing event handlers.

When instead we write classic JavaScript event handlers, we usually write something like this:

function handleResize() {
  console.log("New dimensions: ", window.innerWidth, "x", window.innerHeight);
}

window.addEventListener("resize", handleResize);

In this specific example we first have an event handler, i.e. a function to run when a given event fires. That alone isn’t enough, of course. We’ve only written the function — we still need to specify when it should run. We do that with the last line: we attach an event listener to the window object that, on the resize event, calls the handleResize function, passing it all the available parameters.

ℹ️ Nota

For simplicity, I’m not optimizing the code in these examples. For listeners on events like resize, you should add some debounce or throttle to avoid firing the event too many times, slowing down and making the interface less smooth. I wrote a short guide on how to use debounce and throttle in JavaScript

The problem: we’re in a Single Page Application

In React, though, we need to pay attention. We’re inside a SPA, i.e. a Single Page Application. We never really leave a page. The global-level event handlers you register, timers, etc. stay active unless you terminate them yourself.
The risk, on top of that, is that on every mount of the component you register new, duplicate event handlers.

Registering the event handler with useEffect

We can use the useEffect hook to attach the event listener when the component mounts, and then detach it when the component unmounts. This ensures the code only runs when needed.

The code we can write is as follows:

import { useEffect } from "react";

function handleResize() {
  console.log("New dimensions: ", window.innerWidth, "x", window.innerHeight);
}

export default function App() {
  useEffect(() => {
    window.addEventListener("resize", handleResize);

    return () => {
      window.removeEventListener("resize", handleResize);
    };
  }, []);

  return (
    <div className="App">
      <h1>Your Component</h1>
    </div>
  );
}

I declare the handleResize function outside the component to avoid it being recreated on every render. If I had to use hooks, I’d instead be forced to create it internally, possibly optimizing it with a useCallback hook.

Inside the component I then use the useEffect hook with no dependencies (notice [] as the last parameter) so the code only runs on mount, i.e. when the component is added to the DOM.

I then declare a return that will only run when the component unmounts. This is called a cleanup function.

You can find the example at the following link.

Conclusion

As you’ve seen, we can correctly register event handlers in a React application. The important thing is to remember to remove them when they’re no longer needed, such as when the component unmounts. It’s not the only way to do it, but it’s certainly one of the most common.

Condividi:XLinkedInFacebookWhatsApp