A guide to React Refs

A guide to React Refs

In this article, we'll look into React Refs and understand how they work. We'll also learn how to use and how not to use refs in React projects, and when to use state over refs.

What are Refs?

React Refs (short for reference) provides a way to have access and interact with DOM elements directly. They provide a way to reference a DOM element rendered by a React component and perform actions on it. Due to their direct connection between components and the DOM elements, we can, for example, manipulate elements without rerendering the components, unlike state.

Creating Refs

In functional components, we use the useRef React hook as shown below.

import {useRef} from 'react';

const MyComponent = () => {
    const nameRef = useRef();
}

An initialValue can be passed if we want an initial value of a ref object's current property. We'll discuss more about the current property later on in this article.

import {useRef} from 'react';

const MyComponent = () => {
    const numberRef = useRef(0);
}

How React Refs work

Let's look at an example to understand how we can implement React refs.

import {useRef} from 'react';

const InputForm = () => {
    const nameRef = useRef();
    const ageRef = useRef();

    return (
        <form>
          <div>
            <label>Name</label>
            <input type="text" id="name" ref={nameRef} />
          </div>
          <div>
            <label>Age</label>
            <input type="number" id="age" ref={ageRef} />
          </div>
        </form>
     );
}

After declaring the useRef hook, we connect the ref to the input element using ref={nameRef}. If we were to console.log() this nameRef, for example, we'd find that it's an object with a current property, which is a key property of a ref object. This property holds the actual reference to the DOM element, thus you would see something like {current: input#name} on your console.

So if we were to read the value passed in the name input field, we'd do so using current.value as shown below.

import {useRef} from 'react';

const InputForm = () => {
    const nameRef = useRef();
    const ageRef = useRef();

    const formSubmit = (e) => {
        e.preventDefault();
        console.log(nameRef.current.value);
    }

    return (
        <form onSubmit={formSubmit}>
          <div>
            <label>Name</label>
            <input type="text" id="name" ref={nameRef} />
          </div>
          <div>
            <label>Age</label>
            <input type="number" id="age" ref={ageRef} />
          </div>
          <button type="submit">Submit</button>
        </form>
     );
}

When to use Refs

There are some instances when you might need to use refs. We'll look at a few use cases of refs in this section.

a) Managing focus

We can use refs to manage focus within a component. For instance, we might need to focus on an input field when a certain condition is met, or we might want to control the focus of an element during user interactions. In the example below, we focus on the input field once the page loads.

b) Managing animations and transitionRefs are useful for triggering animations and transitions. This is especially important when you need to coordinate animations or wait for an animation to complete before performing another action.

These are just a few cases of how we can use React refs. The list is not exhaustive, thus there are other use cases. Remember, though, that refs do not trigger a rerender of the component, so use them sparingly as they manipulate the DOM directly, which contradicts React's declarative approach.

When not to use Refs (and use states instead)

Having looked at how refs work and use cases, it can be easier to use refs in situations when we should avoid them. Refs, as mentioned in the official documentation, are an escape hatch, and should be used sparingly.

We should remember that refs, unlike states, don't trigger a rerender of the component. Thus if we need to rerender, for example, when checking for validation on every keystroke and resetting the input fields after submission, we should use state. If we want to use refs in a form, we should use them only if we need to read the value of an input field once and not check on eg every keystroke. Avoid using refs when resetting inputs since this would directly manipulate the DOM.

Passing down refs to other components

We can use forwardRef to access the ref of a child component from its parent component. We can use it as shown below:

// In the App.js file
import "./styles.css";
import { useRef } from "react";
import Input from "./Input";

const inputValues = {
  id: 'amount',
  type: 'number',
  min: '0',
  max: '100',
  step: '5',
  defaultValue: '0'
};

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

  return (
    <div className="App">
      <Input label="amount" input={inputValues} ref={amountInputRef}/>
    </div>
  );
}
// In the Input.js file
import React from "react"

const Input = React.forwardRef((props, ref) => {
    return(
    <div>
        <label>{props.label}</label>
        <input ref={ref} {...props.input} />
      </div>
    )
})

export default Input;

Conclusion

In this article, we've been able to learn about React refs, how they work, how they are used, and how to pass refs to child components. However, though, we've learned that they should be used sparingly as they directly manipulate the DOM, which contradicts React's declarative approach. For more information about refs, check the official React documentation.