useCallback Might Be What You Meant By useRef & useEffect
Sometimes we respond to React element mounts. The first instinct is to useRef & useEffect. But It’s wrong. What you really need is often useCallback.
If you want to respond to a React element’s mounting on the DOM, you may be tempted to use useRef
to get a reference to it and useEffect
to respond to its mounts and unmounts. But it won’t work.
This is because there is no callback or re-render when a component is (un)mounted and attached to ref.current
using useRef
.
The react-hooks
eslint rules would even warn you about that. Notice how neither ref
nor ref.current
as deps of useEffect
trigger it.
See this for yourself in the sandbox:
So what can we do?
useCallback
(link to the official React docs regarding this)
We can rely on passing a regular function wrapped with useCallback
to ref
and react to the latest DOM node reference that it returns.
You can try it in the following sandbox:
Caveat: the
ref
function is guaranteed to be called on mounts and unmounts of elements, even on the first mount, and even if the unmount is as a result of the parent element unmounting.Caveat2: Make sure to wrap
ref
callbacks withuseCallback
.Without it, if you cause a render from the
ref
callback, theref
callback will be triggered again withnull
, potentially leading to an endless loop, because of React internals.To play with this, try removing
useCallback
from the sandbox above and see this other sandbox.
This pattern can be used in several more ways:
useState
Since useState
is a function that is consistent between renders, it can also be used as a ref
.
In this case, the whole node
will be saved in its own state.
As a state, when it changes, it would trigger a re-render and the state can be safely used in the render’s results and as a useEffect
dependency:
useStateRef
Accessing the DOM is expensive, so we want to do this as little as possible.
If you don’t need the whole node
like in the previous hook, you better save only part of it on a state:
As you can see we access the DOM only when the element to which we pass ref
is mounted and only save clientHeight
in this stage.
useRefWithCallback
But sometimes, for the sake of performance, you can do without triggering re-renders on mounts and unmounts of the element that you use it’s ref
.
The following hook doesn't save the node on the state. Instead of using state, it responds to mounts and unmounts directly, so it doesn’t trigger any re-renders.
In the end, if you understand the principle behind using useCallback
as an element ref
you might come up with your own ideas tailored for your specific needs.
Happy Hooking :D