DEV Community

Anton Korzunov
Anton Korzunov

Posted on • Edited on

The same useRef, but it will callback 🤙

For a long while we there were no refs - we had only ref, which was callback based. Something will set a ref by calling it.

 class Example extends React.Component { state = { ref1: null, } ref2 = null; // updating ref1 would trigger update for this component setRef1 = (ref) => this.setState(ref1); // updating ref2 would just set it  setRef2 = (ref) => this.ref2 = ref; render() { return <div ref={ref1}><span ref={ref2}>🤷‍♂️</span></div> } 
Enter fullscreen mode Exit fullscreen mode

That was what we were doing for ages, until createRef comes to the game. React.createRef is more about ref2 way - current ref would just set to, well, ref.current.

Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render.

So - If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a callback ref instead. Ie the old way to _ref.

Hooks API Reference

 const Example = () => { const [ref, setRef] = useState(null); const onRefSet = useCallback(ref => { setRef(ref); ref.current.focus(); // a side effect! }); // well, you can re return <div ref={onRefSet}>😎</div> } 
Enter fullscreen mode Exit fullscreen mode

But later you might try to combine ref-refs and callbacks-refs, and... well that's the road to 🔥hell🔥.

In addition - there is useImperativeHandle which partially could control ref propagation, but every time I was used to use it - it was just a 💩disaster💩.

 function FancyInput(props, ref) { const inputRef = useRef(null); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); // it just does not usually works :P } })); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput); 
Enter fullscreen mode Exit fullscreen mode

LET'S FIX IT!

Introducing use-callback-ref - the same createRef and useRef, but with callback built in.

 import {useCallbackRef} from 'use-callback-ref'; const Example = () => { const ref = useCallbackRef(null, ref => ref && ref.focus()); // that's all return <div ref={ref}>😎</div> } 
Enter fullscreen mode Exit fullscreen mode

It's literally the old good ref with an on-change callback, nothing more.

Why not to use callback-based ref? Well, it's much easier to handle one interface, which would be accessible thought all components that ref would be passed, well, thought - while with setRef only callback would be visible for transitional components. However, that could be a good from isolation point of view.

This simple approach could also help with useImperativeHandle case:

 function FancyInput(props, ref) { const inputRef = useCallbackRef(null, (newValue) => { // notice - this code is __isolated__, and you can move it off this component ref.current = { focus: () => newValue.focus() } // as long as you don't need to use callback-ref anymore - we could simply this case. }); return <input ref={inputRef} ... />; } FancyInput = forwardRef(FancyInput); 
Enter fullscreen mode Exit fullscreen mode

So - Keep in mind that useRef doesn’t notify you when its content changes. Mutating the .current property doesn’t cause a re-render. If you want to run some code when React attaches or detaches a ref to a DOM node, you may want to use a useCallbackRef instead.

  • 300b, and IE11 support
  • based on getters and setters, no Proxies involved

Try it now(codesandbox demo), and call me back later - https://github.com/theKashey/use-callback-ref

And there is the second part of this article


Top comments (1)

Collapse
 
joelstransky profile image
Joel Stransky

This completely solved my issue with connecting Greensock to React-Pixi components.