React callback ref cleanup function
See original GitHub issueAt the time React added callback refs the main use case for them was to replace string refs. A lot of the callback refs looked like this:
<div ref={node => this.node = node} />
With the introduction of createRef
and useRef
this use case is pretty much replaced by these alternatives so the use case of callback refs will shift to advanced use cases like measuring DOM nodes.
It would be nice if you could return a cleanup function from the callback ref which is called instead of the callback with null. This way it will behave more like the useEffect
API.
<div ref={node => {
// Normal ref callback
return () => {
// Cleanup function which is called when the ref is removed
}
}} />
This will be super helpful when you need to set up a Resize-, Intersection- or MutationObserver.
function useDimensions() {
const [entry, setEntry] = useState()
const targetRef = useCallback((node) => {
const observer = new ResizeObserver(([entry]) => {
setEntry(entry)
})
observer.observe(node)
return () => {
observer.disconnect()
}
}, [])
return [entry, targetRef]
}
function Comp() {
const [dimensions, targetRef] = useDimensions()
return (
<pre ref={targetRef}>
{JSON.stringify(dimensions, null, 2)}
</pre>
)
}
Currently, if you want to implement something like this you need to save the observer into a ref and then if the callback ref is called with null you have to clean up the observer from the ref.
To be 99% backward compatible we could call both the callback ref with null and the cleanup function. The only case where it isn’t backward compatible is if currently someone is returning a function and doesn’t expect the function to be called.
function ref(node) {
if (node === null) {
return
}
// Do something
return () => {
// Cleanup something
}
}
Issue Analytics
- State:
- Created 5 years ago
- Reactions:55
- Comments:17 (2 by maintainers)
Yet another attempt at implementing this as a custom hook:
Usage:
It’s a bit more cumbersome to use, since you have to call both
useCallback
anduseCallbackRef
, but at least it allows the deps to be checked by theexhaustive-deps
linting rule.@k15a What do you think of this approach?
I am going to mention one use-case that cannot be solved by any of the workarounds listed here. It happens when you want to use the same ref callback for multiple elements.
As a simple example, let’s say I want to write a ref that adds all elements to an array, and remove them from the array when they are removed from DOM. I should be able to simply write:
I don’t know if other people would think of this use-case as a valid usage of
ref
, but I think it is a beautiful code pattern that both saves performance and provides an easy API.As far as I know, there is no easy workaround for this. One library I know which uses this kind of pattern is
react-hook-form
. It solves the issue by makingregister
a function and calling it with a unique name for each element like<input ref={register("password")} />
. As for me, I was going to build a reusable tooltip hook but it’s not going to be that easy without this functionality.