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
:
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 the 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
:
However, if you compare two instances of the same function, it returns true
:
In other words, the thirdAdd
and the add
are referentially equal.
Table of contents
- Why is referential equality important in React?
- The
useEvent
Hook - Implementing the
useEvent
Hook from RFC
Why is referential equality important in React?
It’s important to know this concept. Different functions with the same code are often created in React. Look at the following code:
React destroys the current version of the handleEvent
function and creates a new version each time AComponent
re-renders.
Where is this a problem?
This is not every efficient in certain cases. Imagine that you have the following scenarios:
- If you use a hook like
useEffect
that take such event handler in its dependency array - A memoized component accepts such an event handler.
In both of these scenarios, you actually 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 and it furthermore affects the performance by either re-rendering a memoized component or by firing the useEffect callback.
You can easily solve this by using useCallback
Hook, as shown below:
The useCallback
Hook memoizes the function, meaning that whenever a function is called with 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/prop, the useCallback
Hook creates a new handler function each time prop/state changes. Take a look at the following:
Now, the function will not be created each time ACompnent is re-rendered. But if someState
changes, it will create a new instance of handleEvent
even when the definition of function remained the same.
Feeling defeated? No worries.
The useEvent
Hook
This is exactly what useEvent
is trying to solve. You can use the useEvent
Hook to define an event handler with always stable function identity. In other words, the event handler will be referentially the same during each re-render.
In other words, the event handler will have the following properties:
- The function will not be re-created each time prop or state changes.
- The function will have access to the latest value of both prop and state.
This is how you would use it:
Since the useEvent
makes sure 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.
Let’s understand it in a bit more detail.
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.
The useLayoutEffect
Hook inside the useEvent
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 theuseLayoutEffect
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 the reason why the function always has the stable referential identity.
You might then ask 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.
- You can’t use functions created with the
useEvent
can’t be used during rendering. For example:
The above code will fail.
Unmounting useEffect vs. useLayoutEffect
The unmounting useEffect
and the useLayoutEffect
will have a different version of the useEvent
handler. Take a look at the following example:
If you run this program, you’ll see the unmounting useLayoutEffect
has old version of the getValue
event handler. Here is the Stackblitz example for you to check: https://react-ts-uuk4dv.stackblitz.io
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!