Anton Korzunov
Posted on June 22, 2019
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
Posted on June 22, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.