DEV Community

loading...

The same useRef, but it will callback

thekashey profile image Anton Korzunov Updated on 3 min read

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>
}

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 doesnt notify you when its content changes. Mutating the .current property doesnt 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>
}

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

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

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);

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>
}

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);

So - Keep in mind that useRef doesnt notify you when its content changes. Mutating the .current property doesnt 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


Discussion (1)

pic
Editor guide
Collapse
joelstransky profile image
Joel Stransky

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