DEV Community

loading...
Cover image for Understanding the use of useRef hook & forwardRef in React

Understanding the use of useRef hook & forwardRef in React

Sajith Pradeep
・4 min read

The useRef hook in react is used to create a reference to an HTML element. Most widely used scenario is when we have form elements and we need to reference these form elements to either print their value or focus these elements etc.

So the {useRef} hook is imported from "react" like other react hooks and we use them inside functional components to create references and this can be assigned to an html element in the jsx by using the "ref" attribute.

An example for using the useRef hook is shown below-

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

const UseRefHookExplained = (props) => {
  // Creating refs for username and password
  const userNameRef = useRef(null);
  const passwordRef = useRef(null);

  // We are also creating a reference to the Login button
  const submitBtnRef = useRef(null);

  // useEffect to set the initial focus to the user name input
  useEffect(() => {
    userNameRef.current.focus();
  }, []);

  // This function is used to handle the key press.
  // Whenever user hits enter it moves to the next element
  const handleKeyPress = (e, inputType) => {
    if (e.key === "Enter") {
      switch (inputType) {
        // Checks if Enter pressed from the username field?
        case "username":
          // Moves the focus to the password input field
          passwordRef.current.focus();
          break;
        // Checks if Enter pressed from the password field?
        case "password":
          // Moves the focus to the submit button
          submitBtnRef.current.focus();
          e.preventDefault();
          break;
        default:
          break;
      }
    }
  };

  // Function to handle the submit click from the button
  const handleSubmit = () => {
    alert("submitted");
  };

  // getting the style as prop from the parent.
  // Basic style to center the element and apply a bg color
  const { style } = props;
  return (
    <div style={style}>
      <h2>Example for using useRef Hook</h2>
      <h3>Login</h3>
      <input
        type="text"
        name="username"
        ref={userNameRef}
        onKeyDown={(e) => handleKeyPress(e, "username")}
      />
      <input
        type="password"
        name="password"
        ref={passwordRef}
        onKeyDown={(e) => handleKeyPress(e, "password")}
      />
      <button ref={submitBtnRef} onClick={handleSubmit}>
        Login
      </button>
    </div>
  );
};

export default UseRefHookExplained;


Enter fullscreen mode Exit fullscreen mode

So the concept of useRef hook is straight forward as you can see in the above code. Follow the following steps -

  1. We import useRef hook from react
  2. We initialize this hook (eg: const inputRef = useRef(null))
  3. The reference created is attached to an html element using the "ref" attribute.

Now we will have a reference to this element readily available to be used to make changes like getting the value, focusing etc

Output
Initial state when the page loads -
image.png

Focus State after entering user name and pressing enter -
image.png

Focus state moving to the button after entering the password and clicking on Enter

image.png

So, this much should be pretty clear by now. Now let us look at a scenario when we will be using another React component for input.

In this case it becomes a little difficult to pass on the reference that we have defined in the parent component as a property to the child (Input component).

React provides us a way to handle this scenario and forward the refs to the child component using React.forwardRef

Let us check the example code to see the changes -
( I have added a comment "//new" to identify the newly added lines)

import React, { useEffect, useRef } from "react";
import Input from "./Input"; // new

const UseRefHookExplained = (props) => {
  // Creating refs for username and password
  const userNameRef = useRef(null);
  const passwordRef = useRef(null);

  // We are also creating a reference to the Login button
  const submitBtnRef = useRef(null);

  // useEffect to set the initial focus to the user name input
  useEffect(() => {
    userNameRef.current.focus();
  }, []);

  // This function is used to handle the key press.
  // Whenever user hits enter it moves to the next element
  const handleKeyPress = (e, inputType) => {
    if (e.key === "Enter") {
      switch (inputType) {
        // Checks if Enter pressed from the username field?
        case "username":
          // Moves the focus to the password input field
          passwordRef.current.focus();
          break;
        // Checks if Enter pressed from the password field?
        case "password":
          // Moves the focus to the submit button
          submitBtnRef.current.focus();
          e.preventDefault();
          break;
        default:
          break;
      }
    }
  };

  // Function to handle the submit click from the button
  const handleSubmit = () => {
    alert("submitted");
  };

  // getting the style as prop from the parent.
  // Basic style to center the element and apply a bg color
  const { style } = props;
  return (
    <div style={style}>
      <h2>Example for using useRef Hook</h2>
      <h3>Login</h3>
      {/* New. Using the Component instead of input element */}
      <Input
        type="text"
        name="username"
        ref={userNameRef}
        onKeyDown={(e) => handleKeyPress(e, "username")}
      />
      {/* New. Using the Component instead of input element */}
      <Input
        type="password"
        name="password"
        ref={passwordRef}
        onKeyDown={(e) => handleKeyPress(e, "password")}
      />
      <button ref={submitBtnRef} onClick={handleSubmit}>
        Login
      </button>
    </div>
  );
};

export default UseRefHookExplained;

Enter fullscreen mode Exit fullscreen mode

Now let us look at the Input.js component

import React from "react";

/* In the functional component, a second argument 
is passed called ref, which will have access to 
the refs being forwarded from the parent */
const Input = (props, ref) => {
  /* assigning the ref attribute in input and spreading 
the other props which will contain type, name, onkeydown etc */
  return <input {...props} ref={ref} />;
};

// wrapping the Input component with forwardRef
const forwardedRef = React.forwardRef(Input);

// Exporting the wrapped component
export default forwardedRef;

Enter fullscreen mode Exit fullscreen mode

So, React.forwardRed provides us a way with which we can still pass on or forward the refs defined in the parent component to the child component.

Hope you learned something new today!

Discussion (10)

Collapse
lukeshiru profile image
LUKE知る

One thing you can do, is put everything on an util to reuse it in every component:

import { forwardRef } from "react";

export const withRef = (displayName, component) =>
    Object.assign(
        forwardRef((props, ref) =>
            component({ ...props, ref })
        ),
        { displayName }
    );
Enter fullscreen mode Exit fullscreen mode

And then you can use it with any component:

const Input = withRef("Input", props => (
    <input {...props} />
));
Enter fullscreen mode Exit fullscreen mode

We take displayName as an argument to make the experience with React DevTools a little better, and we send props and ref together so we can just spread props naturally.

You can also use preact, which doesn't need this kind of stuff, because the ref is always forwarded :D

Collapse
sajithpradeep profile image
Sajith Pradeep Author

Thanks for bringing in this perspective. I will try this out. It makes more sense as there are multiple instances where forwarding the hooks is required and this helps us stick to the DRY principle.

Collapse
karem1986 profile image
Karem

could u please explain what is the DRY principle? and if you have the link to test the code from github that would be awesome please :)

Thread Thread
lukeshiru profile image
LUKE知る

The DRY principle is just an acronym for "Don't Repeat Yourself". Basically is to avoid having code duplication by moving some logic into reusable functions. In this particular scenario, having a function like withRef helps with DRY because we don't need to write that logic every time we have a component like that, we just call the withRef function and done :)

Collapse
psiho profile image
Mirko Vukušić • Edited

Oh, you definetly should mention other uses of useRef! It's really incomplete and useRef is for far more than just refferencing elements. From docs: 'However, useRef() is useful for more than the ref attribute. It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.'.

Collapse
sajithpradeep profile image
Sajith Pradeep Author

Thanks for this comment. Very valuable info. I will definitely take a look at this and update this post with my learning!

Collapse
psiho profile image
Mirko Vukušić • Edited

Just one example that I actually use more than refferencing elements... When you want to avoid running useEffect on mount of the component: stackoverflow.com/questions/594926...

More examples here: dmitripavlutin.com/react-useref-gu...

Thread Thread
sajithpradeep profile image
Sajith Pradeep Author

Thanks for this! I'm sure this will come in handy for me.

Collapse
zakimax profile image
ZakiMax

it's great article

Collapse
sajithpradeep profile image
Sajith Pradeep Author

Glad it helped!