Telerik blogs

Learn how to use the forwardRef function in React to expose DOM nodes inside a component to its parent component.

In React, refs are used for storing values that don’t trigger a re-render when updated. We can also assign refs to DOM elements so that we can reference the ref to manipulate the DOM element assigned to the ref.

Refs can also be assigned components, but we need to do one extra step. We need to forward the ref to the DOM element we want to assign or set the value we want to return when the ref is referenced with forwardRef.

In this article, we will look at how to use the forwardRef function to expose DOM nodes that are inside a component.

Basic Usage

We call the forwardRef function with a render function. A render function returns a group of DOM elements or components.

forwardRef is needed to expose a DOM node in a component to its parent component.

For instance, we write:

import { forwardRef, useRef } from "react";

const CustomInput = forwardRef((props, ref) => {
  const { label } = props;
  return (
    <>
      <label>{label}</label>
      <input ref={ref} />
    </>
  );
});

export default function App() {
  const inputRef = useRef();

  return (
    <div>
      <CustomInput label="Name" ref={inputRef} />
      <button onClick={() => inputRef.current.focus()}>focus</button>
    </div>
  );
}

to define the CustomInput component.

We allow the CustomInput component to be assigned a ref by calling forwardRef with the render function. The function takes the props of the component and the ref.

We assign the ref parameter value as the value of the input’s ref prop so that the input will be returned as the value of inputRef.current when we reference it in App.

props is the props for the CustomInput component. Therefore, the label prop’s value would be Name.

In App, we create a ref with useRef and assign that as the value of CustomInput's ref prop.

Then we assign the onClick prop of the button to call inputRef.current.focus(), where inputRef.current is the input element within the CustomInput component.

Therefore, when we click the focus button, the input will have focus.

Customize the Value of the Ref

We can customize the value of the ref with the useImperativeHandle hook. useImperativeHandle takes the ref, a callback that returns the value we want for the ref, and an array of dependencies to watch for to trigger the callback to call as arguments.

For instance, we write:

import { forwardRef, useImperativeHandle, useRef } from "react";
import { v4 as uuidv4 } from "uuid";
const CustomInput = forwardRef((props, ref) => {
  const { label } = props;
  const inputRef = useRef(null);

  useImperativeHandle(
    ref,
    () => {
      return {
        focus() {
          inputRef.current.focus();
        },
        scrollIntoView() {
          inputRef.current.scrollIntoView();
        }
      };
    },
    []
  );

  return (
    <>
      <label>{label}</label>
      <div>
        <input ref={inputRef} />
      </div>
    </>
  );
});

export default function App() {
  const inputRef = useRef();

  return (
    <div>
      <div>
        <button onClick={() => inputRef.current.focus()}>focus</button>
        <button onClick={() => inputRef.current.scrollIntoView()}>
          scroll into view
        </button>
      </div>
      {Array(100)
        .fill()
        .map((_, i) => {
          const key = uuidv4();
          return (
            <CustomInput
              key={key}
              label={`Name ${i + 1}`}
              ref={i === 99 ? inputRef : undefined}
            />
          );
        })}
    </div>
  );
}

to change CustomInput to call the useImperativeHandle hook.

It takes the ref we want to forward as the first argument. The ref will then be set to the value returned by the callback in the second argument.

We choose to return the focus and scrollIntoView method. focus calls focus on the input element we assigned the inputRef to by setting the ref prop of the input to inputRef.

And in scrollIntoView, we call the input’s scrollIntoView method to scroll the page until the input is visible on the screen.

The third argument is an empty array since we don’t want to return a new value when anything with a reactive value changes, including props and states.

Next, in App, we render 100 CustomInput components. We assign the inputRef in the App component as the ref prop’s value if index i in 99. Otherwise, we don’t assign a ref to it.

Therefore, the last CustomInput rendered, which is the bottommost one, would be assigned inputRef and the rest has no ref assigned to them.

Afterward, we add two buttons. The first button calls inputRef.current.focus() to call focus on the CustomInput that has been assigned App's inputRef.

Likewise, the second button calls scrollIntoView with the same CustomInput. As a result, when we click scrollIntoView, we scroll to the bottom of the page. And when we click focus, the last input is focused on.

Forwarding Refs Through Multiple Components

Refs can be forwarded through multiple components.

For instance, if we write:

import { forwardRef, useRef } from "react";

const CustomInput = forwardRef((props, ref) => {
  const { label } = props;

  return (
    <>
      <label>{label}</label>
      <div>
        <input ref={ref} />
      </div>
    </>
  );
});

const InputGroup = forwardRef((props, ref) => {
  const { label } = props;

  return (
    <>
      <CustomInput ref={ref} label={label} />
      <select>
        <option>foo</option>
        <option>bar</option>
      </select>
    </>
  );
});

export default function App() {
  const inputRef = useRef();

  return (
    <div>
      <div>
        <button onClick={() => inputRef.current.focus()}>focus</button>
      </div>

      <InputGroup label="Name" ref={inputRef} />
    </div>
  );
}

to define the InputGroup component, which renders a CustomInput component.

InputGroup is created by calling the forwardRef function so that it can accept the ref prop.

Then we pass the ref to CustomInput by assigning the ref parameter of its render function to CustomInput's ref prop.

And in CustomInput's render function, we assign its ref parameter to the input’s ref prop.

As a result, the App component’s inputRef value is the input element in CustomInput since that’s where the ref is ultimately forwarded to.


About the Author

John Au-Yeung

John Au-Yeung is a frontend developer with 6+ years of experience. He is an avid blogger (visit his site at https://thewebdev.info/) and the author of Vue.js 3 By Example.

Related Posts

Comments

Comments are disabled in preview mode.