While ref forwarding is well explained in the official documentation, it can be confusing to type it correctly with function components.
1. Typing the reference to be forwarded
The reference is created with the createRef
function.
Unlike the useRef
hook, which creates a MutableRefObject
whose .current
property might be initialized with an undefined
value, createRef
returns an immutable RefObject
so you don't need to bother with the initial value type being different than the final value type.
const refTitle: React.RefObject<Text> = React.createRef<Text>();
// In real life, you don't need to explicitly type refTitle
In this example the reference will point to a Text
component therefore the Text
component is passed to createRef
as a type argument to explicitly type the referenced value.
2. Forwarding the reference to the child component
With the ref attribute of the child component:
const ParentComponent: React.FC = () => {
const refTitle = React.createRef<Text>();
return (
<ChildComponent ref={refTitle} title="Hello, World!" />
);
}
3. Assigning the forwarded ref inside the child component
As said in the documentation:
By default, you may not use the ref attribute on function components because they donβt have instances.
If you want to allow people to take a
ref
to your function component, you can useforwardRef
Then with the forwardRef
function:
interface ChildComponentProps {
title: string;
}
const ChildComponent = React.forwardRef<Text, ChildComponentProps>(
(
{ title }: ChildComponentProps,
ref: React.ForwardedRef<Text>,
): JSX.Element => {
return (
<Text ref={ref}>{title}</Text>
);
},
);
// Name the component for debugging purpose and prevent eslint warning.
ChildComponent.displayName = 'ChildComponent';
Now there's an immutable RefObject
available in the ParentComponent
that points to the Text
component inside the ChildComponent
.
Concrete example
Here is an example with React Native where you can use ref forwarding to focus the device's screen reader on a text once the whole parent component is ready:
Photo by JC Gellidon on Unsplash
Top comments (3)
You should hear from the ether that this helped me :) I looked at many examples and was fiddling quite a while until this worked for me. I used the 3rd pattern.
Fwiw, I found the return type of
ReactElement
to work as the index.d.ts I had didn't appear to exportJSX
(anymore because I've definitely used that in the past).I find it interesting the number of hoops I have to jump through just to implement a keyboard navigation handler in react + TS but I guess that's the world of modern FE development Β―_(γ)_/Β―
the
@types/react
module should declare the globalJSX
namespace here:As you can see
JSX.Element
is an alias ofReact.ReactElement<any, any>
so that's probably why it works for you.I sometimes find myself working harder on typing rather than on the code itself, but if you see typing as the only standardized, accurate and up-to-date documentation you can enforce (with a linter and git hooks), it worth the effort.
Then you still have to deal with the most difficult problem in programming: picking good names.
Yeah, I think I was doing something wrong to not be able to use
JSX.Element
but I forget the exact error.Regarding it being worth the effort I'm still on the fence myself. I think Typescript is a great place to apply the Pareto Principle. I barely use generics and I'm going to let that happen naturally (or not), but I'm not willing to invest as much time as it would take to learn a new language just to please Typescript. But yeah, I like the type safety overall. Applying what you described for
React.forwardRef
today on my project:is about as terse as I'm willing to go for now. Maybe I'll surprise myself and get more fancy later down the road :-)