Understanding React Refs: useRef vs. forwardRef vs. Mutable State

Published on 15.12.2020

In React, managing references to DOM elements and component instances has always been an important part of building complex user interfaces. While React’s state system is ideal for most interactions, there are scenarios where you need to persist values or access DOM nodes directly without causing re-renders. This is where Refs come into play.

In this post, we will explore the following:

  • useRef vs useState: When should you use useRef instead of useState?
  • Managing uncontrolled form inputs with useRef.
  • Passing refs to child components using forwardRef.

By the end of this article, you’ll have a solid understanding of React Refs, and how to use them effectively in your components.


What is a React Ref?

Ref (short for reference) in React is a way to access the DOM or instance of a component directly. It allows you to bypass React’s state-driven re-render cycle and interact with elements or values without causing a re-render.

Ref usage is common for tasks such as:

  • Managing focus on an input element.
  • Measuring the size or position of an element on the screen.
  • Triggering animations or manual changes to DOM elements.

useRef vs. useState: When to Use Each

In React, we primarily use useState to hold values that affect the render cycle of the component, causing the component to re-render when the state changes. However, there are cases where you may want to store a value without triggering a re-render.

useRef is a hook that allows you to store a value that does not trigger re-renders. This makes it ideal for cases where the value does not need to be rendered but must persist across renders.

useRef Example: Managing Mutable Values

Consider an example where you need to track the previous state of a value, but you don’t want to trigger a re-render every time it changes:

import React, { useState, useEffect, useRef } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  useEffect(() => {
    prevCountRef.current = count; // store the current count
  }, [count]); // update the ref when count changes

  return (
    <div>
      <p>Current count: {count}</p>
      <p>Previous count: {prevCountRef.current}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default Counter;

In this example:

  • We use useState to manage the count and useRef to store the previous value of count.
  • The prevCountRef.current does not cause a re-render when it changes, unlike state stored in useState.

When to use useRef instead of useState:

  • Tracking values that don’t need to trigger a re-render: Use useRef to store values that need to persist across renders but don’t affect the UI.
  • Accessing DOM nodes or elements directly: Use useRef for interacting with DOM elements, like focusing an input field.

On the other hand, use useState when you need the value to affect the component’s render and cause re-renders when it changes.


Managing Uncontrolled Form Inputs with useRef

In React, forms can be either controlled or uncontrolledControlled components manage form input values via React state, while uncontrolled components manage form inputs using the DOM itself (via refs).

For uncontrolled form inputs, useRef is ideal, as it allows you to interact with the form elements without explicitly binding the values to the component state.

Example: Using useRef for Uncontrolled Inputs

import React, { useRef } from 'react';

function UncontrolledForm() {
  const nameInputRef = useRef();

  const handleSubmit = (event) => {
    event.preventDefault();
    // Access the value of the input using the ref
    alert(`Name: ${nameInputRef.current.value}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input ref={nameInputRef} type="text" placeholder="Enter your name" />
      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledForm;

In this example:

  • We use useRef to reference the input element (nameInputRef).
  • The form’s value is accessed directly from the DOM using nameInputRef.current.value, which is uncontrolled as opposed to a controlled input that would rely on state.

Uncontrolled components are useful when you need to integrate with third-party libraries or handle large forms without needing to manage each input’s state.


Passing Refs to Child Components with forwardRef

In certain scenarios, you may need to pass a ref from a parent component down to a child component. React provides the forwardRef API to allow a parent component to access the ref of a child component.

Using forwardRef to Pass Refs

The forwardRef function is used to wrap a child component and allow it to receive the ref from its parent.

import React, { useRef } from 'react';

// Child component with forwardRef
const Input = React.forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});

function Parent() {
  const inputRef = useRef();

  const handleFocus = () => {
    // Focus the input field directly from the parent
    inputRef.current.focus();
  };

  return (
    <div>
      <Input ref={inputRef} placeholder="Focus me!" />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}

export default Parent;

In this example:

  • The Input component is wrapped in forwardRef to allow the ref to be passed down from the Parent.
  • The Parent component holds the ref inputRef and can directly interact with the Input field (e.g., focusing the input).

When to use forwardRef:

  • When you need to pass a ref to a child component that is a functional component.
  • When you need to interact with a DOM element inside a child component.

Note that forwardRef is only necessary when you need to expose a ref to a functional component. Class components automatically forward refs via the ref attribute.


Conclusion

Understanding React Refs and when to use them is essential for managing DOM elements and mutable state without unnecessary re-renders. Here’s a summary of the key points covered in this post:

  • useRef is perfect for managing mutable values that don’t need to trigger re-renders. It’s useful for persisting data across renders without affecting the UI.
  • Uncontrolled form inputs can be managed using useRef to access form element values directly, without binding to state.
  • forwardRef is the solution when you need to pass a ref from a parent to a child component, allowing direct access to child DOM elements or component instances.

By using React’s refs appropriately, you can create more efficient, performant components while maintaining cleaner, more predictable state management. Understanding how and when to use these tools will help you build more robust and scalable applications in React.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *