React Hooks: useRef (With Practical Examples!)

React Hooks: useRef (With Practical Examples!)

Hello there👋

This is the second article of the React Hooks Unboxed Series. In this article, we would be discussing the useRef hook with practical examples. By the end of this article, you should know what the useRef hook is, the difference between a ref and a state variable and its relationship to the DOM with practical examples.

An Introduction

useRef, like the name suggests, lets you create a reference to a value. This reference is in the form of an object, called a ref object. The ref object has a current property that stores the value. The ref object is mutable; you can change its current property and read it.

Declare a ref

We declare a ref at the top level of our component. Here's how to declare a ref:

import { useRef } from "react";

const ref = useRef(initialValue);

In the expression on line 3:

ref: is the reference to the value. It stores the ref object.

initialValue: is the value the reference is initialized with. It is ignored after the first render.

Below is an example of how to create a reference for a count.

import { useRef } from "react";

const countRef = useRef(0);

Our countRef is created using the useRef hook. Our count starts from 0 and is therefore initialized with 0, i.e. the countRef's current property is equal to 0. If we were to increase count, we would simply access the count value using the .current method on the countRef object.

// ...
countRef.current++

console.log(countRef.current)
// prints '1' to the console

Difference between state and ref

A ref is similar to state, in that, we can hold and mutate values to be used in a component. However, unlike state, mutation of a ref does not trigger a re-render. This makes useRef suitable for storing mutable values that need to persist across renders without causing the component to re-render.

To illustrate this using our count app:

// App.jsx
import { useRef } from "react";

export default function App() {
  const countRef = useRef(0);

  function Increment() {
    countRef.current++;
    console.log("Count ref:", countRef);
  }

  return (
    <div className="App">
      <p>CountRef is: {countRef.current}</p>
      <button onClick={Increment}>Increment</button>
    </div>
  );
}

When we run our count app and check the console, we would see that when we click the button, nothing seems to happen even though Increment() runs. The countRef does not trigger any change in the UI. However, if we look at the console, we see that the countRef value is actually being updated even though the UI still displays 0.

However, if we were to add state to our component and update it when Increment() runs, we would see a difference. The countRef object updates but the UI also displays the updated countRef value. This because the setState setter function triggers a re-render and the countRef value is able to persist between renders, therefore updating the UI.

// App.jsx
import { useRef, useState } from "react";

export default function App() {
  const countRef = useRef(0);
  const [count, setCount] = useState(0);

  function Increment() {
    countRef.current++;
    setCount(count + 1);
    console.log("State count:", count);
    console.log("Count ref:", countRef);
  }

  return (
    <div className="App">
      <p>State Count is: {count}</p>
      <p>CountRef is: {countRef.current}</p>
      <button onClick={Increment}>Increment</button>
    </div>
  );
}

Here is how that looks:

If we were to click the button again, like I did in the video above, we would notice that there is no UI update of the countRef to 2. The value updates and you can see that in the console but the UI does not. This is because the setState function does not initiate a re-render. The reason this does not happen is because the state, status, doesn't actually change. When React uses Object.is to compare the previous and new state, it sees there's no difference ('increment' === 'increment' resolves to true), and simply doesn't trigger a re-render of our component!

You can learn more about useState here.

useRef and the DOM

Apart from persisting values without initiating a re-render, useRef is also used to access, interact with, and manipulate the DOM.

Here's a simple example demonstrating the use of useRef to manipulate the DOM:

// App.jsx
import { useEffect, useRef } from "react";

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

  useEffect(() => {
    // Manipulate the DOM directly using the ref
    inputRef.current.focus();
  }, []);

  return (
    <div className="App">
      <label htmlFor="comment">Any comment?</label>
      <br />
      {/* Attaching the ref to an input element */}
      <textarea ref={inputRef} name="comment" rows={4} cols={20}></textarea>
    </div>
  );
}

In this example:

  1. useRef(null) creates a ref object, called inputRef, initialized with null.

  2. The ref is attached to a textarea element with the ref={inputRef} attribute.

  3. In the useEffect hook, the focus method is called on the ref to focus on the textarea input element after the window loads.

Recap

In summary, the useRef hook in React is essential for handling mutable values that persist without causing re-renders and useful for interacting directly with the DOM. Unlike state variables, changes to a ref's current property don't trigger re-renders. This provides developers with a powerful tool for optimizing React components in scenarios requiring direct DOM manipulation or value persistence.

Please leave a like if this was helpful. Comment also if you have questions.

Happy Coding🙌