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
render
s of the component - the latest
props
andstate
are 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
useCallback
with no dependencies, so that the reference to the function is the same for eachrender
-
update
handlerRef.current
at 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.