Using a forwardRef component with children in TypeScript

The answer given by aMarCruz works well. However, if you also need to pass custom props to the FancyButton, here is how it can be done.

interface FancyButtonProps extends React.ComponentPropsWithoutRef<'button'> {
    fooBar?: string; // my custom prop
}

const FancyButton = React.forwardRef<HTMLButtonElement, FancyButtonProps>((props, ref) => (
    <button type="button" ref={ref} className="FancyButton">
        {props.children}
        {props.fooBar}
    </button>
));


/// Use later

// You can now get a ref directly to the DOM button:
const ref = React.createRef<HTMLButtonElement>()

<FancyButton ref={ref} fooBar="someValue">Click me!</FancyButton>

Just adding here for completion.


The answers given by aMarCruz and euvs both work, but they lie to consumers a little bit. They say they accept all HTMLButtonElement props, but they ignore them instead of forwarding them to the button. If you're just trying to merge in the children prop correctly, then you might want to use React.PropsWithChildren instead:

import React from 'react';

interface FancyButtonProps {
    fooBar?: string; // my custom prop
}

const FancyButton = React.forwardRef<HTMLButtonElement, React.PropsWithChildren<FancyButtonProps>>((props, ref) => (
    <button type="button" ref={ref} className="fancy-button">
        {props.children}
        {props.fooBar}
    </button>
));

FancyButton.displayName = 'FancyButton';

Or explicitly add a children prop:

interface FancyButtonProps {
    children?: React.ReactNode;
    fooBar?: string; // my custom prop
}

const FancyButton = React.forwardRef<HTMLButtonElement, FancyButtonProps>((props, ref) => (
    <button type="button" ref={ref} className="fancy-button">
        {props.children}
        {props.fooBar}
    </button>
));

FancyButton.displayName = 'FancyButton';

Or if you actually want to accept all the button props and forward them (let consumers choose button type="submit", for example), then you might want to use rest/spread:

import React from 'react';

interface FancyButtonProps extends React.ComponentPropsWithoutRef<'button'> {
    fooBar?: string; // my custom prop
}

const FancyButton = React.forwardRef<HTMLButtonElement, FancyButtonProps>(
    ({ children, className = '', fooBar, ...buttonProps }, ref) => (
        <button {...buttonProps} className={`fancy-button ${className}`} ref={ref}>
            {children}
            {fooBar}
        </button>
    ),
);

FancyButton.displayName = 'FancyButton';

trevorsg, you need to pass the button properties:

import * as React from 'react'

type ButtonProps = React.HTMLProps<HTMLButtonElement>

const FancyButton = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => (
  <button type="button" ref={ref} className="FancyButton">
    {props.children}
  </button>
))

// You can now get a ref directly to the DOM button:
const ref = React.createRef<HTMLButtonElement>()

<FancyButton ref={ref}>Click me!</FancyButton>

ADDED:

In recent versions of TS and @types/react, you can also use React.ComponentPropsWithoutRef<'button'> instead of React.HTMLProps<HTMLButtonElement>