October 13, 2022

forwardRef in React With Examples

In this tutorial, we'll see why and how to use the forwardRef function in React. In the post refs in React you have already seen that using refs you can access the DOM nodes or React elements.

When you use ref on a built-in component that uses DOM elements like <input/>, React will set the ref’s current property to the corresponding DOM node. But what about the scenario where you want to access the DOM nodes of another component say child component.

React.forwardRef

By default, React does not let a component access the DOM nodes of other components. If you want that capability then you have to "forward" the ref from one component to another and then pass it further down to the DOM node, of that another component, for which access is needed. That's where forwardRef is used.

forwardRef React example

Forwarding ref from parent to child component is not a very common scenario but it might be useful when you are creating reusable component libraries.

For example let's create our own Input component which can then be used in other components.

CustomInput.js

const CustomInput = (props) =>{
  return (
    <div>
      <label htmlFor={props.id}>{props.label}</label>
      <input {...props}/>
    </div>
  );
};
export default CustomInput;

This is our CustomInput component that encapsulates a label and input element and uses props to get attribute values from the component where it is used.

Another component is AddUser.js which has two fields name and age in a form. These input fields are defined using our own custom input. When form is submitted it just logs the entered values for the fields.

AddUser.js

import { useState } from "react";
import CustomInput from "../UI/CustomInput";

const AddUser = (props) => {
  const[enteredName, setName] = useState('');
  const[enteredAge, setAge] = useState('');
  const nameChangeHandler = (event) => {
    setName(event.target.value);
  }
  const ageChangeHandler = (event) => {
    setAge(event.target.value);
  }
  const addUserHandler = (event) => {
    event.preventDefault();
    console.log('Entered user name ' + enteredName);
    console.log('Entered user age ' + enteredAge);
    setName('');
    setAge('');
  }

  return (
    <div>
      <form onSubmit={addUserHandler}>
        <CustomInput id="username" label="UserName" type="text" value={enteredName} onChange={nameChangeHandler}></CustomInput>
        <CustomInput id="age" label="Age" type="number" value={enteredAge} onChange={ageChangeHandler}></CustomInput>
        <button type="submit" >Add User</button>
      </form>
    </div>
  );
}
export default AddUser;

So far, we have our own custom input component and another component which makes use of the custom component. This should work and on clicking "Add User" button, it should log the entered values.

Now, you want to add the functionality to focus on the name field once the form is loaded. You may think of doing it by using refs.

So here is the modified AddUser.js file where we have two refs nameRef and ageRef and also uses useEffect hook to call focus on the element once the component is mounted.

import { useRef } from "react";
import { useEffect } from "react";
import { useState } from "react";
import CustomInput from "../UI/CustomInput";

const AddUser = (props) => {
    const[enteredName, setName] = useState('');
    const[enteredAge, setAge] = useState('');
    const nameChangeHandler = (event) => {
        setName(event.target.value);
    }
    const ageChangeHandler = (event) => {
        setAge(event.target.value);
    }
    const addUserHandler = (event) => {
        event.preventDefault();
        console.log('Entered user name ' + enteredName);
        console.log('Entered user age ' + enteredAge);
        setName('');
        setAge('');
    }
    const nameRef = useRef();
    const ageRef = useRef();
    useEffect(() => {
        nameRef.current.focus();
    }, []);
    return (
        <div>
            <form onSubmit={addUserHandler}>
                <CustomInput id="username" label="UserName" type="text" value={enteredName} onChange={nameChangeHandler} ref={nameRef}></CustomInput>
                <CustomInput id="age" label="Age" type="number" value={enteredAge} onChange={ageChangeHandler} ref={ageRef}></CustomInput>
                <button type="submit" >Add User</button>
            </form>
        </div>
    );
}
export default AddUser;

But this change results in an error-

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

As already mentioned, we get this error, because you can't access the DOM nodes of the child component and that's what you are trying to do.

<CustomInput ref={nameRef} />

If you try to put a ref on your own component, by default you will get null thus the error.

Using forwardRef

React framework provides you the flexibility where the component that wants to expose their DOM nodes can explicitly declare it using forwardRef function.

When you pass ref like this

<CustomInput ref={nameRef} />

It just tells React to put the corresponding DOM node into nameRef.current. CustomInput wants to receive nameRef as a ref argument or not has to be defined, by default it won't.

In order to give that forward ref capability, CustomInput component has to be declared using forwardRef.

React.forwardRef function takes the component itself as an argument and returns a React component that has the capability to forward ref.

Here is the changed CustomInput component.

import { forwardRef } from "react";
const CustomInput = forwardRef((props, ref) =>{
    return (
        <div>
            <label htmlFor={props.id}>{props.label}</label>
            <input {...props} ref={ref}/>
        </div>
    );
});

export default CustomInput;

Now CustomInput component can receive the ref from the parent component as the second ref argument.

CustomInput itself passes the ref it received to the <input> inside of it.

That's all for the topic forwardRef in React With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment