DEV Community

Cover image for Forwarding refs in TypeScript
selbekk
selbekk

Posted on • Originally published at selbekk.io

4 1

Forwarding refs in TypeScript

Originally posted on my blog - selbekk.io

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} />
  )
);
Enter fullscreen mode Exit fullscreen mode

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

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

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-wrapped component is:

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

Or more generally:

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

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} />
);
Enter fullscreen mode Exit fullscreen mode

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!

Reinvent your career. Join DEV.

It takes one minute and is worth it for your career.

Get started

Top comments (0)

👋 Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay