circuit

Meet React useEvent(): The Latest and Greatest React Hook

Hailed as the “missing piece in the original Hooks release”, useEvent() will change the way you write modern React.




React hooks are pretty great, can't we agree? For the most part, they allow you to write clean, extensible, and easy-to-understand functional components with clearly defined behavior. When compared to old class-based lifecycle methods and local state management, you'd wonder how React ever got so popular.

The React team certainly sees hooks as the future and looks set to build upon the existing library with the inclusion of useEvent().

What problem does useEvent solve?

In this section, let's have a look at referential equality in callback functions. That may seem confusing but let's go through some code examples to understand what that even means.

New hooks for use to play with!New hooks for use to play with!

Imagine we have a shopping basket component as a Parent component and a “Buy” button as a Child component. We want to “Buy” button (child) to execute some code in the Parent when it is pressed. This can be done with the help of a **callback function. **You've probably seen hundreds of these as they are an extremely common pattern, but this is how it could look:

export const Basket = () => {
  const [total, setTotal] = useState('');

  ... More component logic here ...

  const onBuyClick = () => {
    showTotal(total);
  };

  return <BuyButton onClick={onBuyClick}/>;
}

This is a perfectly valid React callback implementation — but it can be improved.

Every time the “Basket” component is rerendered, it will currently also rerender the “BuyButton” component. This could lead to massive performance loss downstream (just imagine BuyButton has 10 more levels of components, all of which will also be rerendered).

But why does happen? It's because of referential integrity!

Look at the callback function onClickBuy*. *When “Basket” is rerendered, it tears down and recreates this method. While the behavior of this method won't change from render to render, its referential identity will.

Essentially, React doesn't think that the method from the previous render is the same as the method from the current render.

Because (by default) components rerender when their props change, “BuyButton” will rerender because the referential identity of the onClickBuy method has changed.

Referential identity changing on every render and causing children to rerender is the issue that useEvent() aims to solve.

What about useMemo() + useCallback()?

Remember 30 seconds ago when I said that by default components rerender when their props change? The useMemo() and useCallback() change this default behavior.

We can wrap our child component in useMemo() — this means that as long as props are equal to what they were on the previous render, the child component will not rerender.

This is half of the solution as currently onClickBuy will still get past the useMemo shallow props comparison check. What we really want to do is to stop onClickBuy having a new referential identity every render. We can do this by wrapping it in a useCallback(), e.g.,

export const Basket = () => {
  const [total, setTotal] = useState('');

  const onBuyClick = useCallback(() => {
    showTotal(total);
  }, [total]);

  return <BuyButton onClick={onBuyClick}/>;
}

It took a little work, but now we have a decent solution to the referential integrity problem. The child will now only rerender when the referential identity of onBuyClickchanges, and onBuyClickwill only change when its dependency array changes. This is **currently **the best approach to solving the rerendering issue. But it's not perfect.

onBuyClickwill only change when its dependencies change, but what if they change frequently? What if the total state value changes every time a user adds or removes something from the basket? That would mean that the referential integrity of onBuyClick would still be changing a lot, and therefore BuyButton would still end up rerendering a lot.

Oh no! It seems like we've done a lot of work and ended up with almost the same issue! Well… yes. But fear not, because useEvent can solve this issue!

Using useEvent(), we can maintain the same referential identity even if props or state used within the function change.

Once the function is created, it's going to give you the same identity, always. You can think of it almost like a more powerful useCallback(). That's a game-changer when it comes to sorting out child component rerendering optimization.

What does a useEvent implementation look like?

useEvent() is very similar to useCallback(), so the implementation is very similar, except that useEvent() has no dependency array (that means no extra deps array lint errors).

export const Basket = () => {
  const [total, setTotal] = useState('');

  const onBuyClick = useEvent(() => {
    showTotal(total);
  });

  return <BuyButton onClick={onBuyClick}/>;
}

Great, I can't wait to use it!

I'm glad you can see the value of useEvent(), but I'm sorry to burst your bubble, but useEvent() is not currently offered in the current version of React. The good news is that this feature is not big enough to constitute a major release, and is going to be released as a minor, but there is no confirmation yet as to when this is going to be, so keep your eyes peeled!

To read much more about useEvent, head over to the official RFC on the React GitHub.

If you liked this article or found it useful, then a follow would be much appreciated. Alternatively, you could support me on Medium here or buy me a coffee! All support is much appreciated.




Continue Learning