Understanding React Forward: A Complete Guide

Learn how forwardRef enables parent components to access child DOM nodes, when to use it, and how React 19 simplifies ref handling entirely.

What Problem Does forwardRef Solve?

Before understanding forwardRef, it's important to understand the problem it addresses. In React, refs provide a way to access DOM nodes or React elements created in the render method. They're essential for imperative operations like managing focus, measuring element dimensions, triggering animations, or integrating with third-party DOM libraries.

The challenge arises when you want to use a ref with a custom component. Consider a FancyButton component that renders a native button element:

function FancyButton(props) {
 return (
 <button className="FancyButton">
 {props.children}
 </button>
 );
}

If a parent component tries to pass a ref to FancyButton, it won't work as expected. The ref will refer to the component instance, not the underlying button DOM element, because functional components don't have instances by default.

This encapsulation is intentional--components shouldn't rely on each other's DOM structure. However, for highly reusable "leaf" components, this limitation becomes problematic. A button component should behave like a button for accessibility and integration purposes. A custom input component should work with form validation libraries that expect direct DOM access.

For more on building accessible, reusable components, see our guide on selector structure for understanding how CSS selectors interact with React component patterns.

The Basic forwardRef Pattern

Here's how forwardRef solves the problem by forwarding the ref from parent to child:

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

// Now you can get a ref directly to the DOM button:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

When the ref is attached, ref.current will point to the underlying <button> DOM node, just as if you had used the native button element directly.

The forwardRef function receives two arguments: props and ref. The ref is passed as the second argument, and you forward it to the DOM element by attaching it as a JSX attribute.

Forwarding Refs in Higher-Order Components

One of the most powerful use cases for forwardRef is with higher-order components (HOCs). HOCs wrap components to add functionality like logging, authentication, or theming. The challenge is that refs passed to HOCs don't get forwarded to the wrapped component--they refer to the HOC container instead.

Consider a logProps HOC that logs all props to the console:

function logProps(WrappedComponent) {
 class LogProps extends React.Component {
 componentDidUpdate(prevProps) {
 console.log('old props:', prevProps);
 console.log('new props:', this.props);
 }
 render() {
 return <WrappedComponent {...this.props} />;
 }
 }
 return LogProps;
}

When you wrap FancyButton with logProps and try to use a ref, the ref points to LogProps, not FancyButton. This means you can't call methods on the underlying button.

The Solution

To fix this, combine forwardRef with the HOC pattern:

function logProps(Component) {
 class LogProps extends React.Component {
 componentDidUpdate(prevProps) {
 console.log('old props:', prevProps);
 console.log('new props:', this.props);
 }
 render() {
 const { forwardedRef, ...rest } = this.props;
 return <Component ref={forwardedRef} {...rest} />;
 }
 }

 return React.forwardRef((props, ref) => {
 return <LogProps {...props} forwardedRef={ref} />;
 });
}

This pattern ensures that refs are forwarded through the HOC to the wrapped component. Understanding these patterns is essential for building robust web applications with maintainable component architectures.

React 19: Passing Ref as a Prop

React 19 introduces a significant simplification to ref handling. You no longer need forwardRef when passing refs to built-in elements. The ref can now be passed as a regular prop, eliminating the need for the wrapper entirely.

Before React 19 (Using forwardRef)

import React, { useRef, forwardRef } from "react";

const SelectBox = forwardRef(({ options, onChange }, ref) => (
 <select ref={ref} onChange={onChange}>
 {options.map((option) => (
 <option key={option} value={option}>{option}</option>
 ))}
 </select>
));

After React 19 (Passing ref as a Prop)

import React, { useRef } from "react";

const SelectBox = ({ options, onChange, ref }) => (
 <select ref={ref} onChange={onChange}>
 {options.map((option) => (
 <option key={option} value={option}>{option}</option>
 ))}
 </select>
);

This change means less boilerplate, more readable code, and easier maintenance. Ref handling now feels more natural and intuitive for developers building modern web applications.

Why React 19 Ref Changes Matter

Less Boilerplate

No more unnecessary forwardRef wrapping for common use cases with native elements.

More Readable

Code is easier to follow and understand--no special API knowledge required.

Easier Maintenance

Ref interactions are much cleaner and more intuitive for new developers.

Better Composition

Ref handling now feels more natural, perfect for user-friendly forms and smooth transitions.

Ref Cleanup Functions in React 19

React 19 also introduces ref callback cleanup functions. Previously, when a component unmounted, React would call ref callbacks with null. Now, if a ref callback returns a cleanup function, React will call that instead.

Automatic Cleanup Pattern

const setTypeRef = (ref) => {
 if (ref) {
 console.log("setup typeref");
 typeRef = ref;
 }
 return () => {
 console.log("Cleanup function called for type ref");
 typeRef = null;
 };
};

This automatic cleanup reduces the need for manual cleanup code and ensures refs are properly managed during component unmounting.

Before (Manual Cleanup Required)

useEffect(() => {
 console.log("Refs assigned");
 return () => {
 console.log("Cleaning up refs");
 typeRef.current = null;
 };
}, []);

TypeScript Considerations

When working with forwardRef in TypeScript, you need to properly type the ref parameter. The type depends on what element you're forwarding the ref to:

import React, { forwardRef } from 'react';

interface FancyButtonProps {
 label: string;
 onClick: () => void;
}

// Forwarding to HTMLButtonElement
const FancyButton = forwardRef<HTMLButtonElement, FancyButtonProps>(
 ({ label, onClick }, ref) => {
 return (
 <button ref={ref} onClick={onClick}>
 {label}
 </button>
 );
 }
);

TypeScript and Ref Cleanup

React 19's ref cleanup functions introduce TypeScript changes. TypeScript now rejects implicit returns from ref callbacks:

// Before (implicit return - no longer allowed)
const myRef = (node: HTMLDivElement | null) => node && node.focus();

// After (explicit return required)
const myRef = (node: HTMLDivElement | null) => {
 if (node) {
 node.focus();
 }
 // Only explicit returns are accepted
};

Using TypeScript with React ensures type safety throughout your component architecture, catching errors at compile time rather than runtime.

Do: Use for Libraries

Use `forwardRef` when building reusable component libraries that need DOM integration.

Do: Forward Consistently

Forward refs to the appropriate DOM element consistently across your components.

Don't: Overuse Refs

Only create refs when you need imperative access--prefer declarative patterns when possible.

Don't: Break Encapsulation

Avoid accessing DOM nodes for operations that can be done declaratively through props.

Performance Considerations

While forwardRef itself doesn't directly impact rendering performance, how you use refs can:

  1. Avoid Unnecessary Refs: Only create refs when you need imperative access
  2. Memoize Components: Use React.memo with forwardRef to prevent unnecessary re-renders
  3. Cleanup Properly: Ensure refs are cleaned up to prevent memory leaks
  4. Avoid Direct DOM Manipulation: Use React's declarative features when possible
const FancyButton = React.memo(
 React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => (
 <button ref={ref} {...props} />
 ))
);

The memo wrapper prevents re-renders when props haven't changed, while forwardRef maintains ref forwarding capability. Performance optimization is a key consideration when building scalable web applications, and proper ref management contributes to smooth user experiences.

For comprehensive performance optimization strategies, see our guide on optimizing web performance to learn more about React rendering patterns and best practices.

Migration Strategy: From forwardRef to React 19

For projects migrating to React 19, here's a migration strategy:

  1. Identify forwardRef Usage: Find all instances of forwardRef in your codebase
  2. Test Current Behavior: Ensure components with refs work correctly before migration
  3. Convert to Prop Pattern: Remove forwardRef wrapper and pass ref as a regular prop
  4. Update TypeScript Types: Adjust ref types from generic parameters to prop types
  5. Test Thoroughly: Verify ref behavior works as expected in the new pattern
// Before (React 18)
import React, { forwardRef } from "react";
const MyInput = forwardRef(({ label, ...props }, ref) => (
 <div>
 <label>{label}</label>
 <input ref={ref} {...props} />
 </div>
));

// After (React 19)
import React from "react";
const MyInput = ({ label, ref, ...props }) => (
 <div>
 <label>{label}</label>
 <input ref={ref} {...props} />
 </div>
);

Migrating your web applications to React 19 brings significant improvements in developer experience and code clarity. Our team can help guide your migration strategy and ensure a smooth transition.

Frequently Asked Questions

Build Better React Applications

Need help with React component architecture, performance optimization, or migration? Our team specializes in modern React development patterns.

Sources

  1. React Docs: Forwarding Refs - Official React documentation explaining ref forwarding, use cases, and HOC patterns
  2. Saeloun Blog: React 19 Ref Updates - Comprehensive coverage of React 19 ref changes including ref as prop and cleanup functions
  3. React TypeScript Cheatsheet: forwardRef - TypeScript patterns for forwardRef