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:
Learn React with TypeScript - 3rd Edition
NewA comprehensive guide to building modern React applications with TypeScript. Learn best practices, advanced patterns, and real-world development techniques.
View on Amazon