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:

🏃 Play with the code

Learn React with TypeScript - 3rd Edition

New

A comprehensive guide to building modern React applications with TypeScript. Learn best practices, advanced patterns, and real-world development techniques.

View on Amazon
Learn React with TypeScript - Third Edition