Using forwardRef Components with Children in TypeScript: Type Definitions and Best Practices

Dec 03, 2025 · Programming · 7 views · 7.8

Keywords: TypeScript | React | forwardRef | type definitions | children property

Abstract: This article provides an in-depth exploration of handling children properties in forwardRef components when developing with React and TypeScript. It analyzes common error cases, explains the type parameter mechanism of React.forwardRef, and presents multiple solutions including React.HTMLProps, React.ComponentPropsWithoutRef, and React.PropsWithChildren. The discussion extends to proper forwarding of all native attributes, ensuring type safety and component functionality integrity.

Problem Context and Error Analysis

When using React's forwardRef API to create ref-forwarding components, TypeScript developers frequently encounter a typical type error. When attempting to add children properties to forwardRef components, the TypeScript compiler reports errors similar to:

Type '{ children: string; ref: RefObject<HTMLButtonElement>; }' is not assignable to type 'IntrinsicAttributes & RefAttributes<HTMLButtonElement>'. Property 'children' does not exist on type 'IntrinsicAttributes & RefAttributes<HTMLButtonElement>'.ts(2322)

The root cause of this error lies in the type definition of React.forwardRef. When only the first type parameter (the element type the ref points to) is specified, TypeScript infers that the component doesn't accept any additional props, including children. This causes the type system to consider children as invalid properties when they are passed.

Core Solutions

To resolve this issue, you need to provide a second type parameter to React.forwardRef that explicitly specifies the props type the component accepts. Here are several effective solutions:

Solution 1: Using React.HTMLProps

The most direct solution is to use React.HTMLProps to include all native button element 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>
))

This approach ensures the component accepts all standard HTML button properties, including children, onClick, disabled, etc. However, note that this might include some unnecessary properties like form-related attributes.

Solution 2: Using React.ComponentPropsWithoutRef

In newer versions of TypeScript and @types/react, it's recommended to use the more precise React.ComponentPropsWithoutRef type:

import * as React from 'react'

type ButtonProps = React.ComponentPropsWithoutRef<'button'>

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

This type is specifically designed for components that don't need refs. It includes all native button element properties but excludes the ref property (since ref is handled separately through forwardRef).

Solution 3: Using React.PropsWithChildren

If you only need to handle the children property without all native attributes, you can use the React.PropsWithChildren utility type:

import React from 'react';

interface FancyButtonProps {
    fooBar?: string; // custom property
}

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

This approach is particularly suitable for scenarios where custom properties need to be added, ensuring the children property is correctly included in the props type.

Advanced Usage: Property Forwarding

In actual development, it's often necessary to forward all native properties except custom ones to the underlying element. This can be achieved using object destructuring and spread operators:

import React from 'react';

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

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

This pattern ensures:

  1. All native button properties are correctly forwarded
  2. Custom properties are handled separately
  3. className can be extended without being overwritten
  4. Type safety is maintained

Type Parameter Details

Understanding React.forwardRef's type parameters is crucial for correct usage:

Proper type parameter combinations ensure:

  1. ref is correctly forwarded to the target element
  2. All necessary props (including children) are recognized by the type system
  3. Custom properties are properly handled
  4. Code maintains good maintainability and readability

Best Practice Recommendations

Based on the above analysis, the following best practices are recommended:

  1. Always provide complete type parameters to React.forwardRef, including both ref type and props type
  2. Prefer React.ComponentPropsWithoutRef over React.HTMLProps for more precise type definitions
  3. Use interface extension when adding custom properties
  4. Consider using property forwarding patterns to ensure all native properties are properly handled
  5. Set displayName for forwardRef components to facilitate debugging

By following these practices, you can avoid common type errors and write type-safe, maintainable React components.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.