React.forwardRef vs Custom Ref Prop: What Are the Key Advantages?

In React, refs (short for references) are a powerful feature for accessing DOM elements or React components directly. They enable use cases like managing focus, triggering animations, or integrating with third-party libraries that require DOM access. However, when building reusable components, passing refs from a parent to a child component isn’t always straightforward. Two common patterns emerge for this: React.forwardRef and custom ref props (e.g., innerRef, inputRef).

While both solve the problem of "ref forwarding," they differ in syntax, use cases, and advantages. This blog dives deep into both approaches, comparing their strengths, weaknesses, and ideal scenarios. By the end, you’ll understand when to use forwardRef and when to opt for custom ref props.

Table of Contents#

  1. Understanding Refs in React
  2. React.forwardRef: The Native Approach
    • How It Works
    • Key Advantages
    • Practical Examples
  3. Custom Ref Props: The Flexible Alternative
    • How It Works
    • Key Advantages
    • Practical Examples
  4. Head-to-Head Comparison
  5. When to Use Each Approach
  6. Common Pitfalls to Avoid
  7. Conclusion
  8. References

1. Understanding Refs in React#

Before diving into the two patterns, let’s recap what refs are and why they matter.

Refs are React’s way to access the underlying DOM nodes or React elements created by render. They bypass the typical React data flow (props) and allow direct interaction with elements. Common use cases include:

  • Focusing an input (element.focus()).
  • Triggering animations on a DOM node.
  • Integrating with DOM APIs (e.g., canvas, video).
  • Accessing methods of a class component instance.

In functional components, refs are typically created with useRef, while class components use createRef or this.refs.

Example: Basic Ref Usage#

import { useRef } from 'react';
 
function FocusableInput() {
  const inputRef = useRef(null);
 
  const handleClick = () => {
    inputRef.current.focus(); // Access DOM element via ref.current
  };
 
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}

Here, inputRef is attached to the <input> element, allowing the parent to call focus() on it.

2. React.forwardRef: The Native Approach#

React.forwardRef is a built-in function that “forwards” a ref from a parent component to a child component’s DOM element or inner component. It enables the parent to access the child’s underlying DOM node directly via the standard ref prop, without the child explicitly defining a custom prop.

How React.forwardRef Works#

  • A component wrapped with forwardRef receives two arguments: props and ref (instead of just props).
  • The child component attaches this forwarded ref to its internal DOM element or component.
  • The parent can then pass a ref to the child using the standard ref prop, just like with a native DOM element.

Example: Forwarding a Ref with React.forwardRef#

Let’s create a reusable CustomButton component that forwards its ref to the underlying <button> element:

import { forwardRef } from 'react';
 
// Wrap the component with forwardRef to receive the ref
const CustomButton = forwardRef((props, ref) => {
  return (
    <button ref={ref} {...props}>
      {props.children}
    </button>
  );
});
 
// Parent component using CustomButton
function ParentComponent() {
  const buttonRef = useRef(null);
 
  const handleClick = () => {
    buttonRef.current.focus(); // Focus the button via the forwarded ref
  };
 
  return (
    <div>
      <CustomButton ref={buttonRef} onClick={handleClick}>
        Click Me
      </CustomButton>
    </div>
  );
}

Here, CustomButton forwards the ref to the native <button> element. The parent uses ref={buttonRef} to access it, just like with a standard DOM element.

Key Advantages of React.forwardRef#

1. Native Ref Prop Usage#

forwardRef lets components accept the standard ref prop, aligning with developer expectations. Users of your component can pass refs using ref={...}, just like they would for native elements (e.g., <input ref={...}>). This is critical for usability, especially in component libraries.

2. Compatibility with React Features#

forwardRef works seamlessly with useImperativeHandle, a hook that lets you customize the instance value exposed to parent components via ref. Instead of exposing the entire DOM element, you can expose specific methods.

Example with useImperativeHandle:

import { forwardRef, useImperativeHandle } from 'react';
 
const CustomInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
 
  // Customize the ref value exposed to the parent
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    clear: () => {
      inputRef.current.value = '';
    }
  }));
 
  return <input ref={inputRef} {...props} />;
});
 
// Parent can now call .focus() and .clear() on the ref
function Parent() {
  const inputRef = useRef(null);
 
  return (
    <div>
      <CustomInput ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>Focus</button>
      <button onClick={() => inputRef.current.clear()}>Clear</button>
    </div>
  );
}

Here, useImperativeHandle ensures the parent only gets focus and clear methods, not the entire DOM element—encapsulating implementation details.

3. Avoids Prop Name Collisions#

With custom ref props (e.g., innerRef), you risk naming conflicts if the component already uses innerRef for another purpose. forwardRef uses the native ref prop, eliminating this issue.

4. Ideal for Component Libraries#

If you’re building a component library (e.g., UI kits), forwardRef is essential. Users expect to pass refs to library components as they would to native elements. For example, Material-UI and Chakra UI extensively use forwardRef for this reason.

3. Custom Ref Props: The Flexible Alternative#

A custom ref prop involves passing a ref to a child component via a non-standard prop name (e.g., innerRef, inputRef, or refProp). The child explicitly accepts this prop and attaches it to its internal DOM element or component.

How Custom Ref Props Work#

  • The child component defines a prop (e.g., innerRef) to accept the ref.
  • The parent passes a ref to the child using this custom prop (e.g., <Child innerRef={myRef} />).
  • The child attaches the ref to its internal element (e.g., <input ref={props.innerRef} />).

Example: Custom Ref Prop (innerRef)#

Let’s recreate the CustomButton using a custom ref prop instead of forwardRef:

// Child component with a custom ref prop
const CustomButton = (props) => {
  // Accept innerRef as a prop and attach it to the button
  return (
    <button ref={props.innerRef} {...props}>
      {props.children}
    </button>
  );
};
 
// Parent component using the custom ref prop
function ParentComponent() {
  const buttonRef = useRef(null);
 
  const handleClick = () => {
    buttonRef.current.focus(); // Focus via the custom innerRef
  };
 
  return (
    <div>
      <CustomButton innerRef={buttonRef} onClick={handleClick}>
        Click Me
      </CustomButton>
    </div>
  );
}

Here, CustomButton uses innerRef instead of the native ref prop. The parent passes the ref via innerRef={buttonRef}.

Key Advantages of Custom Ref Props#

1. Flexibility with Multiple Refs#

Custom ref props let you pass multiple refs to a child component. For example, a component with two internal inputs could accept inputRef1 and inputRef2 props.

Example: Multiple Custom Refs

const FormComponent = (props) => {
  return (
    <div>
      <input ref={props.usernameRef} placeholder="Username" />
      <input ref={props.passwordRef} placeholder="Password" type="password" />
    </div>
  );
};
 
// Parent using multiple refs
function Parent() {
  const usernameRef = useRef(null);
  const passwordRef = useRef(null);
 
  return (
    <FormComponent
      usernameRef={usernameRef}
      passwordRef={passwordRef}
    />
  );
}

2. Compatibility with Class Components#

Class components do not natively support forwardRef (though you can use React.forwardRef with class components, it’s less intuitive). Custom ref props work seamlessly with class components:

class ClassComponent extends React.Component {
  render() {
    return <input ref={this.props.innerRef} />;
  }
}
 
// Usage: <ClassComponent innerRef={myRef} />

3. Explicit Intent#

Custom ref props make the ref’s purpose explicit. A prop named inputRef clearly indicates it references an input element, whereas ref alone is ambiguous (e.g., does it reference the component itself or an inner element?).

4. Backward Compatibility#

For projects using React versions older than 16.3 (when forwardRef was introduced), custom ref props are the only option for ref forwarding.

4. Head-to-Head Comparison#

FeatureReact.forwardRefCustom Ref Props
Ref Prop NameUses native ref prop.Uses custom prop (e.g., innerRef, inputRef).
Multiple RefsNot directly supported (requires useImperativeHandle for multiple exposed methods).Supports multiple refs via distinct prop names.
Class Component CompatibilityWorks but less intuitive; requires forwardRef wrapper.Native support; no extra setup needed.
Use with useImperativeHandleSeamless (designed to work together).Possible but less idiomatic.
Component LibrariesIdeal (users expect standard ref prop).Risky (users may not know to use innerRef).
Prop CollisionsNo (uses reserved ref prop).Possible (e.g., if innerRef is repurposed).

5. When to Use Each Approach#

Use React.forwardRef When:#

  • Building reusable components/libraries: Users expect to pass refs via the standard ref prop.
  • You need to support useImperativeHandle: To expose a custom interface (e.g., specific methods) instead of the raw DOM element.
  • Avoiding prop name conflicts: The native ref prop is reserved and won’t clash with other props.
  • Aligning with React best practices: forwardRef is the modern, idiomatic way to forward refs in functional components.

Use Custom Ref Props When:#

  • You need multiple refs: Pass distinct refs (e.g., inputRef, buttonRef) to a single component.
  • Working with class components: Easier to integrate than forwardRef for class-based child components.
  • Explicit intent is critical: A custom prop name (e.g., searchInputRef) clarifies the ref’s purpose.
  • Backward compatibility: Supporting React versions <16.3 (where forwardRef is unavailable).

6. Common Pitfalls to Avoid#

For React.forwardRef:#

  • Forgetting to wrap the component: If you forget to wrap a component with forwardRef, it won’t receive the ref argument, and the parent’s ref will be undefined.
  • Over-exposing DOM elements: Avoid exposing raw DOM elements unless necessary. Use useImperativeHandle to expose only needed methods (e.g., focus(), scroll()).

For Custom Ref Props:#

  • Prop name collisions: Choose unique prop names (e.g., myComponentRef instead of innerRef) to avoid conflicts with other props.
  • Implicit ref expectations: Document custom ref props clearly (e.g., “Pass a ref to inputRef to access the input element”). Users won’t know to use innerRef unless told.

7. Conclusion#

Both React.forwardRef and custom ref props solve the problem of ref forwarding, but they excel in different scenarios:

  • React.forwardRef is the modern, idiomatic choice for reusable components and libraries, offering native ref support and seamless integration with hooks like useImperativeHandle.
  • Custom ref props provide flexibility for multiple refs, explicit intent, and compatibility with class components or older React versions.

When in doubt, default to React.forwardRef for new projects—especially if building components for others to use. Reserve custom ref props for cases requiring multiple refs or backward compatibility.

8. References#