The Power of forwardRef in React.js: When and How to Use It

The Power of forwardRef in React.js: When and How to Use It

Introduction

React.js makes it easier to create user interfaces, but it can be difficult to directly access the DOM and component instances. forwardRef helps solve this problem by making it simpler to pass and manage references.

Direct Access Challenges in Component Libraries

Let's say we're developing a CustomInput component to expose from our library, intending to let developers control focus or integrate with third-party libraries requiring direct DOM access. A common challenge arises when trying to allow the parent component to directly interact with the CustomInput's underlying DOM node.

Consider the initial approach without forwardRef:

// CustomInput component without forwardRef
function CustomInput(props) {
  // Attempting direct DOM access
  return <input {...props} />;
}

// Attempting to use ref in a parent component
function ParentComponent() {
  const inputRef = React.createRef();

  // Attempt to focus the input on button click fails
  // because CustomInput doesn't forward the ref
  const focusInput = () => {
    inputRef.current.focus(); // Error: inputRef.current is null
  };

  return (
    <>
      <CustomInput ref={inputRef} />
      <button onClick={focusInput}>Focus the input</button>
    </>
  );
}

In this scenario, the parent component's attempt to focus the CustomInput fails because ref is not automatically passed through. Older solutions, such as using props to pass down callbacks or using context, make things more complicated and hide the intended direct control features.

These challenges reveal React's initial refs limitations, emphasizing the need for forwardRef to improve component interaction and library usability.

Introduction of forwardRef

forwardRef lets refs be sent to a DOM node or component instance inside a component, making direct interactions easier and helping components work together better.

How forwardRef Works

forwardRef wraps a component, enabling it to receive a ref and forward it to a child DOM element or component. This is crucial for operations requiring direct access, like input focus or animation control.

Practical Example

Using forwardRef simplifies focusing on a nested button component:

import React, { forwardRef } from 'react';

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

// Parent component using FancyButton
const ParentComponent = () => {
  const buttonRef = React.useRef(null);

  const focusButton = () => buttonRef.current.focus();

  return (
    <>
      <FancyButton ref={buttonRef}>Click Me</FancyButton>
      <button onClick={focusButton}>Focus the Fancy Button</button>
    </>
  );
};

Appropriate Use Cases for forwardRef

forwardRef is especially helpful for working with direct DOM access, managing focus, animations, and integrating with libraries focused on the DOM.

It often comes in handy for making reusable component libraries that need encapsulation and direct instance access.

However, be careful with overusing refs, as the React documentation says:

Do not overuse refs. You should only use refs for imperative behaviors that you can’t express as props: for example, scrolling to a node, focusing a node, triggering an animation, selecting text, and so on.

If you can express something as a prop, you should not use a ref. For example, instead of exposing an imperative handle like { open, close } from a Modal component, it is better to take isOpen as a prop like <Modal isOpen={isOpen} />.