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.

Vitali Zaidman
Welldone Software
3 min readJan 14, 2021

--

setRef returned from 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.

react-hooks warns about using useRef with useEffect

See this for yourself in the sandbox:

A sandbox where useRef as deps of useEffect fails to trigger it on time

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:

A sandbox where useCallback is used as a ref and detects renders on time

Caveat: theref 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 with useCallback.

Without it, if you cause a render from the ref callback, the ref callback will be triggered again with null, 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

--

--