React useRef
useRef creates a mutable box that persists for the lifetime of a component without triggering a re-render when its value changes.
Refs vs State
useRef(initialValue) returns a plain object with a single mutable property, current, set to initialValue. Reassigning ref.current does not schedule a re-render and does not affect what's on screen — it simply persists across renders, making refs the right tool for values a component needs to remember but never needs to display.
Accessing DOM Nodes
The most common use of useRef is grabbing a reference to an actual DOM element. Pass the ref object to a JSX element's ref attribute, and React sets ref.current to that underlying DOM node right after it mounts — letting you call imperative DOM methods like focus() or measure its size.
- Focusing an input programmatically
- Measuring an element's size or position
- Storing a timer or interval id so it can be cleared later
- Remembering a previous prop or state value across renders
- Storing any mutable value, like a render count, that must not trigger a re-render
Focusing an input on mount
import { useRef, useEffect } from "react";
function SearchBox() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} placeholder="Search..." />;
}
export default SearchBox;
Refs are just as useful for values that have nothing to do with the DOM. A timer id, a WebSocket instance, or a previous value you want to compare against — anything mutable that should survive re-renders without itself causing one — belongs in a ref rather than in state.
Storing an interval id in a ref
import { useRef, useState } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
function start() {
if (intervalRef.current !== null) return;
intervalRef.current = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
}
function stop() {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return (
<div>
<p>{seconds}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
export default Timer;
Tracking the previous value of a state variable
import { useState, useEffect, useRef } from "react";
function PreviousValue() {
const [count, setCount] = useState(0);
const previousCountRef = useRef();
useEffect(() => {
previousCountRef.current = count;
}, [count]);
return (
<div>
<p>
Now: {count}, before: {previousCountRef.current}
</p>
<button onClick={() => setCount((prev) => prev + 1)}>+</button>
</div>
);
}
export default PreviousValue;
Exercise: React useRef
What's the key difference between updating a ref's .current and calling a useState setter?