Using a Forwarded Ref Internally
Sometimes we need to do things imperatively in React - for example, setting focus to an input
element.
A useRef
hook allows us to access HTML elements and invoke their methods imperatively. There is also forwardRef
for forwarding the ref from a reusable component.
What if we want to use the ref internally within a reusable component and also forward the ref? This post covers how to do this.
A reusable Input
component
We have a reusable Input
component that contains an input
element with a tooltip:
type Props = React.ComponentPropsWithoutRef<"input"> & {
tooltip?: React.ReactNode;
};
export const Input = (props: Props) => {
const internalRef = React.useRef<HTMLInputElement>(null);
const [
popperElement,
setPopperElement
] = React.useState<HTMLDivElement | null>(null);
const { styles, attributes } = usePopper(internalRef.current, popperElement);
const [showTooltip, setShowTooltip] = React.useState(false);
return (
<>
<input
ref={internalRef}
className="input"
{...props}
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
/>
<div
ref={setPopperElement}
className="tooltip"
style={{ ...styles.popper, display: showTooltip ? "block" : "none" }}
{...attributes}
>
{props.tooltip}
</div>
</>
);
};
react-popper
is used for the tooltip. react-popper
requires a reference to the input
element - we use a variable called internalRef
that uses useRef
for this.
So, our reusable Input
component uses the input
elements ref internally.
Adding forwardRef
At the moment, consumers of the Input
component haven’t got a ref to the input
element to do things like setting focus to it.
We need to add forwardRef
to Input
to give consumers a ref to the input
element:
export const Input = React.forwardRef< HTMLInputElement, Props>((props, ref) => { const internalRef = React.useRef<HTMLInputElement>(null);
...
return (
<>
<input
ref={internalRef}
...
/>
...
</>
);
});
The problem with this is the forwarded ref, ref
, isn’t wired up to the input
element ref, internalRef
.
useImperativeHandle
There is a useImperativeHandle
hook in React that we can use to wire the refs up:
const internalRef = React.useRef<HTMLInputElement>(null);
// 💥 Type 'HTMLInputElement | null' is not assignable to type 'HTMLInputElement'
React.useImperativeHandle(ref, () => internalRef.current);
useImperativeHandle
takes in the forwarded ref and a function that returns the resolved ref.
The above gives a type error though. This is because the ref isn’t expected to have a null
value. 😞
To resolve the type errors, we can use the generic parameters for useImperativeHandle
:
React.useImperativeHandle<HTMLInputElement | null, HTMLInputElement | null>(
ref,
() => internalRef.current
);
A consumer of the Input
component can now use its ref to set focus to the input
element:
const inputRef = React.useRef<HTMLInputElement>(null);
React.useEffect(() => {
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
return (
<Input
ref={inputRef}
type="text"
tooltip="Enter something interesting"
/>
);
Nice! 😊
The code from this post is available in Codesandbox in the link below:
Did you find this post useful?
Let me know by sharing it on Twitter.If you to learn more about using TypeScript with React, you may find my course useful: