useRef stores a value that survives between renders without causing a new render when it changes. It returns a plain JavaScript object with a single current property you can read or write freely. That makes it the right tool for values your component must remember but the UI never displays.
What useRef returns
Call useRef at the top level of your component and pass the starting value as the only argument. React creates the ref object once and returns that same object on every later render, so anything you store in it sticks around.
| Part | Behavior |
|---|---|
| initialValue | The starting value of current. Ignored after the first render. |
| current | A mutable property you can read and write at any time. |
| Return object | The same object on every render of the component. |
Unlike state, the object is mutable. Assigning a new value to current updates it immediately, and React does not re-render in response.
Why a normal variable is not enough
A component function runs from scratch on every render, so a local variable declared inside it resets every time. A timer ID stored that way is lost as soon as any re-render happens.
import { useRef } from "react";
function Chat() {
const timeoutRef = useRef(null);
function handleSend() {
timeoutRef.current = setTimeout(() => console.log("Sent"), 2000);
}
function handleCancel() {
clearTimeout(timeoutRef.current);
}
}The ref keeps the same object across renders, so the timer ID survives until it is cleared. The cancel handler can always find it, which a plain variable could not guarantee.
A ref does not trigger a re-render
The core tradeoff is that React never notices when current changes. That is the feature, but it also means a ref cannot drive the screen.
A counter built on a ref updates its number silently and never refreshes the label, because no re-render happens. A value used for rendering belongs in state. See useRef vs useState for the decision rule.
A stopwatch: state and a ref working together
A stopwatch needs two kinds of data. The elapsed time is shown on screen, so it is state. The interval ID is only used to stop the timer, so it is a ref.
import { useState, useRef } from "react";
function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => setNow(Date.now()), 10);
setStartTime(Date.now());
setNow(Date.now());
}The two state variables drive the display, because React re-renders when they change. The ref only stores the timer ID, which never appears in the JSX and does not need a render.
function handleStop() {
clearInterval(intervalRef.current);
}
const secondsPassed =
startTime !== null && now !== null ? (now - startTime) / 1000 : 0;
return (
<>
<p>Time passed: {secondsPassed.toFixed(3)}</p>
<button onClick={handleStart}>Start</button>
<button onClick={handleStop}>Stop</button>
</>
);
}The screen shows the elapsed time updating every 10 milliseconds. Storing the interval ID in a ref avoids an extra re-render when the timer is created or cleared.
When to use a ref
Reach for a ref when the component must step outside React and remember something the UI does not render.
- Storing timer or interval IDs.
- Holding DOM nodes, which the next articles cover in detail.
- Caching values only event handlers or Effects read.
Rules that keep refs safe
- Do not read or write ref.current during render, except for one-time lazy initialization.
- Use state when the value must appear in the UI or influence the next render.
- Treat refs as an escape hatch, not the main data flow.
What to learn next
Refs are most often used to touch DOM nodes React manages for you. Continue with focusing an input with useRef.
Rune AI
Key Insights
- useRef returns an object with a current property that persists across renders.
- Changing current does not trigger a re-render, unlike state.
- Store values in a ref only when they are not used for rendering.
- Read and write refs in event handlers or Effects, not during render.
- Use state for anything the UI must display or react to.
Frequently Asked Questions
Does changing a ref trigger a re-render?
When does useRef return a new object?
Can I read ref.current during render?
Conclusion
useRef is an escape hatch that keeps a mutable value alive across renders without forcing a re-render. Use it for values that only event handlers and Effects need, such as timer IDs and DOM nodes, and switch to state whenever the value must appear on screen.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.