snow-mountain

The React useEvent() Hook: Why You Need It and How To Use It


Last updated on
ReactWeb Development

Summary (TL;DR): In this article, we are going to discuss the useEvent() hook in React, and explain how to use it. We will provide an explanation of why it was proposed in the first place, explain why the problem can’t be solved with useMemo() and useCallBack() and offer you some examples.

On May the 4th, 2022, the React team published an RFC (Request For Comments) proposing the release of a new hook known as useEvent().

React hooks are exceptionally great tools, and there are no doubts. For the best part, they allow you to write clean and easy-to-understand functional components with clearly defined actions.

React sees hooks as the future of the framework and intensively works on building new ones to solve existing problems, and useEvent() is one of those. 

In this article, we will talk about what problem the useEvent() hook solves and also see different examples of how best to use this hook.

While You Are Here, Why Not Learn How To Build a WordPress Plugin or Theme With React?

Table of Contents

Prerequisites
Why Was This Hook Proposed?
Why We Can’t Use useMemo() and useCallBack() To Solve This Problem
Examples
Refactor Code With useEvent()

Prerequisites

Readers will need knowledge on how useCallBack(), useRef(), and useMemo() hooks are used in React to handle events.

Why Was the Hook Introduced?

Take for example we are creating an e-commerce website, of course we will need a shopping basket component. The shopping basket component would be the parent component and the purchase button will be the child component.

We want the purchase button (child component) to execute the code more likely to call a function in the Parent whenever it is clicked. We can do this with the callback() function in React. Commonly, this is how it will look:

export const ShoppingBasket = () => {
  const [sumTotal, setSumTotal] = useState("");
  //component logic here ...
  const onPurshase = () => {
    showSumTotal(sumTotal);
  };

  return <Purshase onClick={onPurshase} />;
};

whenever the ShoppingBasket component is re-rendered, it automatically re-renders the PurchaseButton component also, which in the long run will lead to performance issues all over the application. 

“A question arises why exactly does this happen? it happens because of referential identity.”

More questions arise, like, how does Referential Identity Affect the code? Or, what exactly is referential identity?.

 In literal form, the reference identity is simply the use of the object reference as identity. 

Look at the callback function onPurchase, Whenever Shopping Basket is re-rendered, it cuts down and regenerates a method. While there will be no change from one render to another, its referential identity will certainly change.

Fundamentally, React thinks differently, and in its thinking process, it doesn’t know that the method from the initial render is the same as the method from the present render.

By default, components are meant to re-render whenever their properties change, and that’s normal in React. which is why the purchaseButton will re-render because the referential identity of onPurchase method has changed.

The occasional change in referential identity due to every render which causes children to re-render is the problem useEvent() was created to solve, and adding to its benefit is helping you write cleaner codes.

Why We Can’t Use useMemo() and useCallBack() To Solve This Problem

Despite the useEvent() coming to our rescue, does it mean we have had this issue all along without a solution? No, usememo() and usecallback() could solve the problem. Wrapping the child component with useMemo(), as long as the properties remain equal to what their initial render was, the child component will not re-render.

Problem solved? Yes in small projects, not really in larger projects.

Our initial aim is to stop onPurchase from having a new referential identity whenever there is a re-render. Wrapping it with useCallback() is our favorite solution:

export const ShoppingBasket = () => {
  const [sumTotal, setSumTotal] = useState("");
  //component logic here ...
  const onPurshase = () => {
    showSumTotal(sumTotal);
  };

  return <Purshase onClick={onPurshase} />;
};

In this case, the child will now only re-render when the referential identity of onPurchase changes and onPurchase will change only when there is a change in the dependency array, but what if there is a frequent change? More like the sumTotal state value changes as the user adds something to the Shopping-Basket, this could make the referential identity of onPurchase change so much, and trigger a re-render of the child component.

That’s why the useEvent() hook is referred to as a “React sensor hook that subscribes a handler to events”. Utilizing the useEvent(), we will be able to maintain the same referential identity even if props or state used within the function change. This is what it looks like:

export const ShoppingBasket = () => {
  const [sumtotal, setSumTotal] = useState("");

  const onpurshace = useEvent(() => {
    showTotal(sumtotal);
  });

  return <Purchase onClick={onpurshace} />;
};

Examples

If you are a React developer you might find yourself stuck in one of these situations, where you want your code to react to user events, but because they happen very often you want to optimize them.

If you are creating a chat, for example, you might have your recent message stored in a state that gets updated with every keystroke like this:

function Chat() {
  const [message, setMessage] = useState("");
  const onSend = () => {
    sendMessage(message);
  };

  return (
    <div>
      <input onSend={Send} />
    </div>
  );
}

When the user is ready, they can send their final message through the onSend() function above.

When it comes to optimization, you can wrap onSend with useCallback() to make sure the function only gets recreated when your state changes.

const onSend = useCallback(() => {
  sendMessage(message);
}, [message]);

Well, this is great, because now input will not re-render unless onSend gets re-created, but at the same time this optimization is not precisely what we would hope for, because onSend will be re-created every single time your messages change.

message=” common” =Recreated
message=” Common ninja” =Recreated
message=” Common ninja blog” =Recreated

How was this fixed before the useEvent() hook? The useRef() hook was a saver.

const send = useRef(null);
useLayoutEffect(() => {
  send.current = () => sendMessage(message);
});

Send here will store our event handler so that onSend will always have the same function you are passing down to input.

const onSend = useCallback((...args) => {
  return send.current(...args);
}, []);

Although this works, it comes at a cost of losing a lot in code readability, and here is why the useEvent() React hook came to our rescue.

Refactor Code With useEvent()

Use event is very similar to useCallback(), so the implementations look much alike, except for the fact that useEvent() has no dependency array.

If we go back to what we usually write in React applications, the main difference usually lies in the number of re-renders that will happen in the code below:

const onSend = useCallback(() => {
  sendMessage(message);
}, [message]);

With useCallback() when we create a new function and the message changes, the input will re-render anytime it does.

With useEvent():

const onSend = useEvent(() => {
  sendMessage(message);
});

We are only going to trigger one rendering and the component mounts, the reason being that the function onSend will always be the same and that’s even the reason why we could drop the dependencies entirely.

In terms of what useEvent() returns, it behaves just like a normal function. So you could also pass in arguments, like RoomID, and call it inside the input, as you normally would:

const onSend = useEvent((roomID) => {
  sendMessage(roomID, message);
});

useEvent() is very useful for handlers and events and you will always find them a lot with useEffect.

If we go back to our chat example, let’s say we want to show a success alert and empty our current message:

function Chat() {
  const [message, setMessage] = useState("");
  const onSend = useEvent((roomID) => {
    sendMessage(roomID, message);
  });
  return (
    <div>
      <input onSend={onSend} />
    </div>
  );
}
useEffect(() => {
  showToast("change chat");
  setMessage("");
}, [roomID]);

This would be great, but if our toast reads from our current theme context const Theme= useContext(ThemContext);, we will need our effect to add it as a dependency, like this:

useEffect(() => {
  showToast(theme, "change chat");
  setMessage("");
}, [roomID, theme]);

and the code above will break it.

We just don’t want our toast to appear whenever our theme changes, but then this could be taken outside the effect and converted to a callback():

const Theme = useContext(ThemContext);
const onChangeRoom = useCallback(() => {
  showToast(theme, "change chatroom");
  setMessage("");
}, [theme]);

useEffect(() => {
  onChangeRoom();
}, [roomID]);

Remember we said useEvent() prevents re-renders so basically it will make more sense here if the function is passed down to a component.

const onChangeRoom = useEvent(() => {
  showToast(theme, "change chatroom");
  setMessage("");
});

Conclusion 

We have been able to talk about the few questions React has and useEvent answers, it births high hopes for better readability. We would prefer calling it UseHandler() because it handles events perfectly.