The Latest Ref Pattern in React
by Kent C. Dodds

In my post "How React Uses Closures to Avoid Bugs" I explain some of the trade-offs React made when they switched from classes and lifecycles to functions and hooks. I want to dig a little deeper on this topic.
In that post, I share the following example:
function useDebounce(callback, delay) {const callbackRef = React.useRef(callback)React.useLayoutEffect(() => {callbackRef.current = callback})return React.useMemo(() => debounce((...args) => callbackRef.current(...args), delay),[delay],)}
I want to talk a bit about this pattern that my friend Yago (who created the original hook) likes to call "The Latest Ref Pattern."
The pattern itself is pretty simple. Here's the part that's the pattern:
Curious aboutconst callbackRef = React.useRef(callback)React.useLayoutEffect(() => {callbackRef.current = callback})
useLayoutEffect
?That's it. That's the pattern.
So why would you want to do this? Well, let's think about when you use useRef
.
You use useRef
whenever you want to keep track of a value, but not trigger a
re-render when you update it. So in our case, we're trying to keep track of
callback
. The reason for this, is we want to make sure that we're always
calling the latest version of the callback
rather than one from an old render.
But why don't we use useState
instead? Could we keep track of this latest
callback value in an actual state value? We don't want to use useState
because
we don't need to trigger a component re-render when we update to the latest
value. In fact, in our case if we tried, we'd trigger an infinite loop (go
ahead, try it 😈).
And because we don't need or want a re-render when we update the callback
to
the latest value, it means we also don't need to (and really shouldn't) include
it in a dependency array for useEffect
, useCallback
, or in our case
useMemo
. This is an important point, so I want to dive into it a bit.
It's really important that you follow the
eslint-plugin-react-hooks/exhaustive-deps
rule and always include all
dependencies. But you should skip the current
value of a ref. So don't ever
do this:
// ❌ don't ever do thisReact.useEffect(() => {}, [ref.current])
This is because updating a ref doesn't trigger a re-render anyway, so React
can't call the effect callback or update memoized values when the ref is
updated. So if you include ref.current
in the dependency array, you'll get
surprising behavior that's difficult to debug. As a side-note, because the ref
itself is a stable object, it doesn't make a difference if you include the ref
object itself in your dependency array:
// 🤷♂️ doesn't make a difference whether you include the ref or not.React.useEffect(() => {}, [ref])
You can run into some serious bugs if you don't include all your non-ref deps though, so just please, don't ignore the linting rule for this.
Conclusion
Before using "the latest ref pattern" everywhere, I suggest you get a good understanding for what it is you're side-stepping, so if you haven't already, give "How React Uses Closures to Avoid Bugs" a read-through. That'll help you get a better idea of when it can be useful to use this particular pattern. I'd love to hear situations when you find this pattern useful. Tweet @ me.
Take care 👍