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 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.
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 🔥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);
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
onlycallback
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 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)
This completely solved my issue with connecting Greensock to React-Pixi components.