Quiz

What is the `useRef` hook in React and when should it be used?

Topics
React

TL;DR

The useRef hook in React is used to create a mutable object that persists across renders. It can be used to access and manipulate DOM elements directly, store mutable values that do not cause re-renders when updated, and keep a reference to a value without triggering a re-render. For example, you can use useRef to focus an input element:

import { useEffect, useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
useEffect(() => {
inputEl.current?.focus();
}, []);
return <input ref={inputEl} type="text" />;
}

What is the useRef hook in React and when should it be used?

useRef preserves a mutable container across renders for values that should not themselves trigger rendering.

Introduction to useRef

The useRef hook in React is a function that returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component. Updating ref.current does not trigger a re-render.

A few important rules:

  • Avoid reading or writing ref.current during rendering because it makes output depend on mutable data React does not track. The documented exception is predictable one-time initialization, such as filling a null ref with a lazily created object.
  • It is fine (and expected) to read or write ref.current inside event handlers, effects, or callbacks.

Key use cases for useRef

Refs primarily bridge to imperative host objects or retain non-rendered mutable values between component calls.

Accessing and manipulating DOM elements

One of the primary use cases for useRef is to directly access and manipulate DOM elements. This is particularly useful when you need to interact with the DOM in ways that are not easily achievable through React's declarative approach.

Example:

import { useEffect, useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
useEffect(() => {
inputEl.current?.focus();
}, []);
return <input ref={inputEl} type="text" />;
}

In this example, useRef holds the input DOM node and the Effect focuses it after the node has been committed. Optional chaining handles the case where the ref is not attached.

Storing mutable values across renders

useRef can also be used to store any mutable value that should persist across renders without causing one. Common examples are interval/timeout IDs, the previous value of a prop or state, an instance of a non-React object (e.g. a chart or map controller), or a counter used inside event handlers.

import { useEffect, useRef, useState } from 'react';
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
function start() {
if (intervalRef.current !== null) return;
intervalRef.current = window.setInterval(() => {
setSeconds((value) => value + 1);
}, 1000);
}
function stop() {
window.clearInterval(intervalRef.current);
intervalRef.current = null;
}
useEffect(() => {
return () => window.clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>{seconds} seconds</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}

Here the interval ID must survive renders but does not belong in the UI, so a ref is appropriate. The displayed elapsed time remains state because changing it must trigger a render.

Refs in React 19

React 19 changed how refs interoperate with components in two important ways:

ref is now a regular prop — forwardRef is no longer required

In function components, ref is now an ordinary prop. You can accept it directly in your props and pass it to a DOM node (or to another component) without wrapping the component in React.forwardRef. forwardRef still works but is no longer necessary, and React plans to deprecate, then remove, it in a future major.

// React 19+: just accept `ref` as a prop.
function FancyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// Usage is unchanged at the call site:
function Parent() {
const inputRef = useRef(null);
return <FancyInput ref={inputRef} placeholder="Type..." />;
}

Cleanup functions from ref callbacks

Ref callbacks may now return a cleanup function, which React runs when the ref detaches (similar to useEffect). This removes the need for the older "called with null" pattern.

import { useCallback } from 'react';
function reportFocus(event) {
console.log('Focused:', event.currentTarget.name);
}
function FocusableInput() {
const inputRef = useCallback((node) => {
node.addEventListener('focus', reportFocus);
return () => node.removeEventListener('focus', reportFocus);
}, []);
return <input ref={inputRef} name="email" />;
}

Further reading

Exercises

Check your understanding
Beta
Check your understanding Exercise
Check your understanding Exercise

Which values are appropriate to keep in a ref? Select all that apply.