How to use ref callbacks in React
I'm sure you're familiar with the useRef hook.
And I'm willing to wager you're accustomed to using it like this
const inputRef = useRef<HtmlInputElement>(null);
return <input ref={inputRef} />;
And if you wanted to focus said input, you'd probably reach for an useEffect.
const inputRef = useRef<HtmlInputElement>(null);
useEffect(function focusInput() {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
What if I told you there is a better way? 😏
Welcome ref callbacks
React refs are not just objects. They can be functions too!
type RefCallback<T> = {
bivarianceHack(instance: T | null): void;
}["bivarianceHack"];
type Ref<T> = RefCallback<T> | RefObject<T> | null;
So we can do something like this
const inputRef = useRef<HtmlInputElement>(null);
<input ref={(el) => (inputRef.current = el)} />;
Basically, every render, this ref
function will run and set the value with the variable el
as a HtmlInputElement
node.
But this is not really good is it? We don't want to re-run this over and over as an user interacts with our application.
Well, we can thank useCallback for coming to the rescue!
So, we have all the ingredients for a really nice cake 🍰
Let's mix them together!
Focusing an Input
const inputRef = useRef<HtmlInputElement>(null);
const inputRefCallback = useCallback(
(el: HtmlInputElement | null) => {
if (el) {
el.focus();
}
},
[]
)
<input ref={inputRefCallback} />
Seriously, Remember memoization
Remember to use memoization so you're not focusing your input on every render, not only would that be wrong, it would be really annoying.
Measuring a container
type Dimensions = { width: number; height: number };
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 });
const divRefCallback = useCallback(
(el: HtmlDivElement | null) => {
if (el) {
const { width, height } = el.getBoundingClientRect();
setDimensions({ width, height });
}
},
[]
)
<div ref={divRefCallback}>{children}</div>
Starting a game on a canvas
const canvasRefCallback = useCallback(
(el: HtmlCanvasElement | null) => {
if (el) {
new GameEngine(el).start();
}
},
[]
)
<canvas ref={canvasRefCallback} />
And that's it! 🚀
Hope you learned something new and this becomes useful on your dev journey.