We know that there is a so-called closure trap in the use of Hooks, consider the following code.
We expect sendMessage to pass the latest value of text after a click.
However, in reality, the effect of the click is always sendMessage('') because the callback function is cached by useCallback, forming a closure.
This is the closure trap.
One solution to the above code is to add a dependency for useCallback.
But after doing so, whenever the dependency (text) changes, useCallback will return a new onClick reference, which defeats the purpose of useCallback caching function references.
The advent of the closure trap has increased the threshold for getting started with Hooks and made it easier for developers to write buggy code.
Now, the official React team is going to solve this problem.
useEvent
The solution is to introduce a new native Hook – useEvent.
He is used to define a function that has 2 features.
- it keeps the reference consistent across multiple
renders of the component - the latest
propsandstateare always available within the function
The above example is modified with useEvent as follows.
The onClick always points to the same reference when the Chat component is rendered multiple times.
And the latest value of text is always available when onClick is triggered.
It’s called useEvent because the React team believes that the main application scenario for this Hook is to wrap event handler functions.
useEvent implementation
The implementation of useEvent is not difficult, the code is similar to the following.
|
|
The whole consists of two parts.
-
returning a
useCallbackwith no dependencies, so that the reference to the function is the same for eachrender -
update
handlerRef.currentat the right time so that the actual function executed is always the latest reference
Differences between ## and open source Hooks
Many open source Hooks libraries already implement similar functionality (e.g. useMemoizedFn in ahooks)
The main differences between useEvent and these open source implementations are that useEvent is focused on the single scenario of handling event callback functions, while useMemoizedFn is focused on caching various functions.
So the question is, if the functions are similar, why does useEvent have to limit itself to a single scenario?
The answer is: for more stability.
Whether useEvent can get the latest state and props depends on when handlerRef.current is updated.
In the above mockup, the logic for useEvent to update handlerRef.current is placed in the useLayoutEffect callback.
This ensures that handlerRef.current is always updated after the view has finished rendering.
The event callback is apparently triggered after the view has finished rendering, so the latest state and props are consistently available.
Note: the actual update timing in the source code is earlier, but that doesn’t affect the conclusion here
Looking at useMemoizedFn in ahooks, fnRef.current is updated when useMemoizedFn is executed (i.e., when the component renders).
When React18 has concurrent updates enabled, the number and timing of component render is uncertain.
So the timing of updates to fnRef.current in useMemoizedFn is also uncertain.
This increases the potential risk when used with concurrent updates.
It can be said that useEvent limits the application scenario by limiting the update timing of handlerRef.current, which ultimately leads to stability.
Summary
useEvent is still in the RFC (Request For Comments) phase.
Many enthusiastic developers have suggested names for this Hook, for example: useStableCallback.

Another example: useLatestClosure.

From these nomenclature, it is clear that they have expanded the application scenario of useEvent.
As we know from the analysis in this article, expanding the application scenario means increasing the risk of error when developers use it.