Using useRef for DOM Access
In this tutorial, we will learn exactly what useRef is used for in React 19 and when it is the correct tool to reach for. Many beginners misuse useRef as a replacement for state or as a way to bypass React’s data flow. By the end, you will understand the correct mental model for refs and how to use them safely and intentionally.
You will learn:
- What
useRefactually does - When you should use
useRef - When not to use it
- Common beginner mistakes and modern alternatives
- How to think about refs in a React 19 world
What We Will Learn
- What
useRefreally is - How refs differ from state
- Valid DOM access use cases
- Non DOM use cases for refs
- Common anti patterns
- The correct mental model for
useRef
What useRef Really Is
useRef creates a persistent, mutable container that React keeps between renders.
const ref = useRef(initialValue);That container looks like this:
{
current: initialValue
}React does not track changes to ref.current.
This is the most important rule:
Updating a ref does not cause a re render.
Why useRef Exists
React is declarative. The UI should be a function of data.
However, some things cannot be expressed declaratively, such as:
- Focusing an input
- Scrolling to an element
- Measuring layout
- Controlling media playback
- Integrating with imperative browser APIs
useRef is React’s escape hatch for these cases.
When You SHOULD Use useRef
Below are the correct, modern use cases.
1. Accessing DOM Elements
This is the most common and intended use.
Example: Focusing an Input
import { useRef } from "react";
function FocusInput() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus();
}
return (
<>
<title>useRef DOM Access</title>
<meta
name="description"
content="Using useRef to access DOM elements in React 19"
/>
<input ref={inputRef} placeholder="Type here..." />
<button onClick={focusInput}>Focus Input</button>
</>
);
}What is happening:
- React renders the input
- React assigns the DOM node to
inputRef.current - You imperatively call a browser API
This is a valid use of useRef.
2. Reading Layout and Measurements
Some values only exist after the DOM is rendered.
import { useRef } from "react";
function BoxSize() {
const boxRef = useRef(null);
function logSize() {
const box = boxRef.current;
console.log(box.offsetWidth, box.offsetHeight);
}
return (
<div>
<div
ref={boxRef}
style={{ width: 200, height: 100, background: "lightgray" }}
/>
<button onClick={logSize}>Log Size</button>
</div>
);
}You are not controlling the UI with the ref. You are reading information from the DOM.
That is correct usage.
3. Persisting Values Across Renders
Refs can store any mutable value that should survive renders but should not trigger UI updates.
import { useRef } from "react";
function RenderCounter() {
const renderCount = useRef(0);
renderCount.current++;
return <p>Rendered {renderCount.current} times</p>;
}Why this works:
- The ref persists across renders
- Updating
.currentdoes not re render - React keeps the value for you
This is useful for timers, IDs, previous values, and counters.
4. Storing Imperative Handles
Refs are often used to store things like:
- timers
- animation instances
- observers
- media players
Example:
const intervalRef = useRef(null);
intervalRef.current = setInterval(() => {
console.log("tick");
}, 1000);Again, no UI logic involved.
When NOT To Use useRef
React 19 makes many old ref patterns unnecessary.
These are anti patterns.
1. DON’T Use useRef for UI State
Incorrect:
const count = useRef(0);
function increment() {
count.current++;
}Refs do not re render the UI.
If the value affects what the user sees, use state or server data.
2. DON’T Replace State with useRef
If you need the UI to update, refs are the wrong tool.
Bad idea:
const isOpen = useRef(false);Correct idea:
const [isOpen, setIsOpen] = useState(false);3. DON’T Read Refs During Render for Logic
Refs are imperative, not declarative.
Avoid:
if (inputRef.current) {
// render logic
}Rendering should depend on props, state, or server data, not refs.
4. DON’T Use Refs to Avoid React Patterns
Using refs to bypass React is a code smell.
If you find yourself mutating refs to “force” behavior, stop and rethink the design.
Common Beginner Mistakes
Forgetting .current
Incorrect:
inputRef.focus();Correct:
inputRef.current.focus();Assuming Refs Exist on First Render
Refs are null during the first render.
Only access them after React commits the DOM.
Overusing useRef
If your component has many refs controlling logic, it is likely doing too much imperatively.
A Helpful Mental Model
Think of useRef as:
A box that React holds for you, but does not look inside.
- React will not re render when it changes
- React will not track it
- React will not optimize it
It is outside React’s reactive system.
useRef in the React 19 Mental Model
Use:
use()for async data- Actions for mutations
- State for UI
- JSX for metadata
useReffor imperative escape hatches
Each tool has a narrow, intentional purpose.
Summary Table
| Task | Use useRef? |
Why |
|---|---|---|
| Focusing an input | Yes | Imperative DOM access |
| Reading element size | Yes | DOM measurement |
| Persisting mutable values | Yes | No re render needed |
| Rendering UI values | No | Use state or data |
| Managing application state | No | Breaks React data flow |
| Fetching data | No | Use use() or Actions |
| Triggering UI updates | No | Refs do not re render |
Conclusion
useRef is a powerful but narrow tool.
In React 19, you should use it only when you need to step outside React’s declarative model and interact with the DOM or other imperative systems. It is not a replacement for state, not a shortcut for logic, and not a way to avoid React patterns.
Used correctly, useRef keeps your components clean, predictable, and aligned with modern React best practices.