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.
useEvent
HookuseEvent
Hook from RFCuseEvent
Hook?useEffect
vs. useLayoutEffect
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.
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:
useEffect
that takes an event handler in its dependency arrayIn 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.
useEvent
HookThe 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:
You’d use the useEvent
Hook as follows:
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.
useEvent
Hook from RFCThe 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.
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>; }
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.
Although the useEvent
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 useEvent
Hook, reviewing the scenarios in which you should and shouldn’t use it.
It’s definitely worth keeping an eye on the useEvent
Hook, and I’m looking forward to eventually being able to integrate it into my applications. I hope you enjoyed this article. Happy coding!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowBuild scalable admin dashboards with Filament and Laravel using Form Builder, Notifications, and Actions for clean, interactive panels.
Break down the parts of a URL and explore APIs for working with them in JavaScript, parsing them, building query strings, checking their validity, etc.
In this guide, explore lazy loading and error loading as two techniques for fetching data in React apps.
Deno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
3 Replies to "What you need to know about the React useEvent Hook RFC"
In the conclusion useEffect is mentioned. It should be useEvent, no?
Hey TJ, thanks for catching the typo! We’ve just fixed it. Happy coding.
Why can’t you set `handlerRef.current` outside of the `useLayoutEffect` hook and rid of the `useLayoutEffect` hook altogether?