Indermohan Singh JavaScript developer interested in Angular, RxJS, and Ionic framework.

What you need to know about the React useEvent Hook RFC

4 min read 1226

Useevent Hook React RFC

In React, referential equality is an important concept that impacts how often the components in your application re-render. In this article, we’ll explore the useEvent Hook from React, which allows you to define an event handler with a function identity that is always stable, helping to manage referential equality in your applications.

It’s important to note that at the time of writing, the useEvent Hook is not available for use and is currently being discussed by React community.

Referential equality in JavaScript

In JavaScript, you can compare if two values are equal using the identity operator, which is also called strict equality. For example, in the following code, you’re comparing value a with b:

a === b; // strict equality or indentity operator

The result is a Boolean value that tells you if value a is equal to b or not.

For primitive data types, the comparison is made using actual values. For instance, if a = 10, and b = 10, the identity operator will return true.

Like objects and functions, complex values are a reference type. Even if they have the same code, two functions are not equal. Take a look at the following example where the identity operator will return false:

let add = (a, b) => a + b;
let anotherAdd = (a, b) => a + b;
console.log(add === anotherAdd); // false

However, if you compare two instances of the same function, it returns true:

let thirdAdd = add;
console.log(add === thridAdd); // true

In other words, the thirdAdd and the add are referentially equal.

Why is referential equality important in React?

Understanding referential equality in React is important because we often create different functions with the same code. For example, consider the following code:

function AComponent() {
  // handleEvent is created and destroyed on each re-render
  const handleEvent = () => {
    console.log('Event handler');
  }
  // ...
}

React destroys the current version of the handleEvent function and creates a new version each time AComponent re-renders. However, in certain scenarios, this approach is not very efficient, for example:



  • You use a Hook like useEffect that takes an event handler in its dependency array
  • You have a memoized component that accepts an event handler

In both of these scenarios, you want to maintain a single instance of the event handler. But, every time a re-render happens, you get a new instance of the function, which further impacts the performance, either re-rendering a memoized component or firing the useEffect callback.

You can easily solve this by using the useCallback Hook, as shown below:

function AComponent() {
  const handleEvent = useCallback(() => {
    console.log('Event handled');
  }, []);
  // ...
}

The useCallback Hook memoizes the function, meaning that whenever a function is called with a unique input, the useCallback Hook saves a copy of the function. Therefore, if the input doesn’t change during re-render, you get back the same instance of the function.

But, the moment your event handler depends on a state or prop, the useCallback Hook creates a new handler function each time it changes. For example, take a look at the following code:

function AComponent() {
  const [someState, setSomeState] = useState(0);
  const handleEvent = useCallback(() => {
    console.log('log some state: `, someState);
  }, [someState]);

  // ...
}

Now, each time a component is re-rendered, the function will not be created. But, if someState changes, it will create a new instance of handleEvent, even when the definition of the function remained the same.

The useEvent Hook

The useEvent Hook tries to solve this problem; you can use the useEvent Hook to define an event handler with a function identity that is always stable. In other words, the event handler will be referentially the same during each re-render. Essentially, the event handler will have the following properties:

  • The function will not be recreated each time prop or state changes
  • The function will have access to the latest value of both the prop and state

You’d use the useEvent Hook as follows:


More great articles from LogRocket:


function AComponent() {
  const [someState, setSomeState] = useState(0);
  const handleEvent = useEvent(() => {
    console.log('log some state: `, someState);
  });
  // ...
}

Since the useEvent Hook ensures that there is a single instance of a function, you don’t have to provide any dependencies.

Implementing the useEvent Hook from RFC

The following example is an approximate implementation of useEvent Hook from RFC:

// (!) Approximate behavior

function useEvent(handler) {
  const handlerRef = useRef(null);

  // In a real implementation, this would run before layout effects
  useLayoutEffect(() => {
    handlerRef.current = handler;
  });

  return useCallback((...args) => {
    // In a real implementation, this would throw if called during render
    const fn = handlerRef.current;
    return fn(...args);
  }, []);
}

The useEvent Hook is called with each render of the component where it is used. With each render, the handler function is passed to the useEvent Hook. The handler function always has the latest values of props and state because it’s essentially a new function when a component is rendered.

Inside the useEvent Hook, the useLayoutEffect Hook is also called with each render and changes the handlerRef to the latest values of the handler function.

In the real version, the handlerRef will be switched to the latest handler functions before all the useLayoutEffect functions are called.

The final piece is the useCallback return. The useEvent Hook returns a function wrapped in the useCallback Hook with an empty dependency array []. This is why the function always has the stable referential identity.

You might be wondering how this function always has the new values of props and state. If you take a closer look, the anonymous function used for the useCallback Hook uses the current value of the handlerRef. This current value represents the latest version of the handler because it is switched when the useLayoutEffect is called.

When shouldn’t you use the useEvent Hook?

There are certain situations where you shouldn’t use the useEvent Hook. Let’s understand when and why.

For one, you can’t use functions created with the useEvent Hook during rendering. For example, the code below will fail:

function AComponent() { 
  const getListOfData = useEvent(() => {
    // do some magic and return some data
    return [1, 2, 3];
  });

  return <ul>
    {getListOfData().map(item => <li>{item}</li>}
  </ul>;
}

Unmounting useEffect vs.  useLayoutEffect

The unmounting useEffect Hook and the useLayoutEffect Hook will have a different version of the useEvent handler. Take a look at the following example:

function Counter() {
  const [counter, setCounter] = React.useState(0);
  const getValue = useEvent(() => {
    return counter;
  });
  React.useLayoutEffect(() => {
    return () => {
      const value = getValue();
      console.log('unmounting layout effect:', value);
    };
  });
  React.useEffect(() => {
    return () => {
      const value = getValue();
      console.log('unmounting effect:', value);
    };
  });
  return (
    <React.Fragment>
      Counter Value: {counter}
      <button onClick={() => setCounter(counter + 1)}>+</button>
    </React.Fragment>
  );
}

If you run this program, you’ll see the unmounting useLayoutEffect has old version of the getValue event handler. Feel free to check out the Stackblitz example.

Conclusion

Although the useEffect Hook isn’t yet available for use, it is definitely a promising development for React developers. In this article, we explored the logic behind the useEffect Hook, reviewing the scenarios in which you should and shouldn’t use it.

It’s definitely worth keeping an eye on the useEffect Hook, and I’m looking forward to eventually being able to integrate it into my applications. I hope you enjoyed this article. Happy coding!

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Indermohan Singh JavaScript developer interested in Angular, RxJS, and Ionic framework.

Leave a Reply