selbekk

Forwarding refs in TypeScript

Forwarding refs in TypeScript

May 17, 2020
2 min read

Learn how you can forward refs in React and TypeScript!

When you're working on a component library, or just creating reusable components in general, you often end up creating small wrapper components that only adds a css class or two. Some are more advanced, but you still need to be able to imperatively focus them.

This used to be a hard problem to solve back in the days. Since the ref prop is treated differently than others, and not passed on to the component itself, the community started adding custom props named innerRef or forwardedRef. To address this, React 16.3 introduced the React.forwardRef API.

The forwardRef API is pretty straight-forward. You wrap your component in a function call, with is passed props and the forwarded ref, and you're then supposed to return your component. Here's a simple example in JavaScript:

const Button = React.forwardRef(
  (props, forwardedRef) => (
    <button {...props} ref={forwardedRef} />
  )
);

You can then use this component like ref was a regular prop:

const buttonRef = React.useRef();
return (
  <Button ref={buttonRef}>
    A button
  </Button>
);

How to use forwardRef with TypeScript

I always screw this up, so I hope by writing this article I can help both you and me to figure this out.

The correct way to type a forwardRef component is:

type Props = {};
const Button = React.forwardRef<HTMLButtonElement, Props>(
  (props, ref) => <button ref={ref} {...props} />
);

Or more generally:

const MyComponent = React.forwardRef<
  TheReferenceType, 
  ThePropsType
>((props, forwardedRef) => (
  <CustomComponentOrHtmlElement ref={forwardedRef} {...props} />
));

It was a bit un-intuitive at first, because it looks like you can pass a regular component to ForwardRef. However, regular components don't accept a second ref parameter, so the typing will fail.

I can't count how often I've done this mistake:

type Props = {};
const Button: React.RefForwardingComponent<
  HTMLButtonElement,
  Props
> = React.forwardRef(
  (props, ref) => <button ref={ref} {...props} />
);

This is a mistake, because the RefForwardingComponent is the type of the render function you create (the one that receives props and ref as arguments), and not the result of calling React.forwardRef.

In other words - remember to pass your type variables directly to React.forwardRef! It will automatically return the correct type for you.

Another gotcha is the order of the type variables - it's the ref type first, then the props type. It's kind of counter-intuitive to me, since the arguments to the render function is the opposite (props, ref) - so I just remember it's the opposite of what I'd guess. 😅

I hope this article helped you figure out this pesky typing issue that have gotten me so many times in a row. Thanks for reading!

All rights reserved © 2024