If you want setting ref to trigger a rerender, you can just pass setState
updater as a ref prop. This code will give children
access to root DOM node, and will not fall into infinite re-rendering or anything:
const [root, setRoot] = useState();
return (
<div ref={setRoot}>
<RootContext.Provider value={useMemo(() => root, [root]))}>
{root ? children : null}
</RootContext.Provider>
</div>
);
Finally, if you implement some kind of ref merging (when you have a forwardRef
/ innerRef
, but also need te DOM node for yourself), you should take care to preserve the guarantees native ref provides, because they are there for a reason. Almost all ref merging mechanisms I've seen in the wild miss some points we've discussed today. The web is full of tutorials that offer you subtly broken solutions. A library with 22K stars fails to do it right. Here's my best shot at this problem, and I'm still not sure it ticks all the boxes:
function useExternRef(externRef) {
const stableRef = useRef();
return useMemo(() => ({
get current() {
return stableRef.current;
},
set current(el) {
stableRef.current = el;
setRef(el, externRef);
},
}), [externRef]);
}
Knowing this, I wouldn't be comfortable with any advanced ref patterns (conditional refs / side effects) on non-DOM components.
Now on to a brief recap:
ref
prop is added.ref
prop is removed.refs
conditionaly and even move them between nodes.useLayoutEffect
and lifecycle hooks is well defined.useState
setterref.current
in useLayoutEffect
cleanup is unsafe.ref
prop in components you didn't write.Phew. Now I think we really know everything about react refs.