Understanding useRef: DOM Access in React
Understanding useRef
~10 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
useRef has two very different but equally important use cases: getting direct access to a real DOM element, and "remembering" a value across multiple render passes WITHOUT that triggering a re-render (unlike useState).
Use case 1: direct access to a DOM element
Normally in React you only describe WHAT the interface should look like, and never touch the actual DOM manually. Sometimes, though, you do need direct access – for example, to automatically focus an input field as soon as the page loads. That's what useRef is for:
import { useRef, useEffect } from 'react';
function SearchField() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // real DOM element, directly addressable
}, []);
return <input ref={inputRef} type="text" />;
}useRef(null) creates an object with exactly one property: current, initially null. When the same object is passed as the ref prop to a JSX element, React automatically fills inputRef.current with the real DOM node once it exists – from then on, you can call regular DOM methods like .focus(), .scrollIntoView(), or .value on it.
Extending App.jsx: an auto-focused search field
Let's build this directly into our project – a search field that filters the product list AND gets focused automatically on load. Replace src/App.jsx:
import { useEffect, useRef, useState } from 'react';
import ProductCard from './components/ProductCard';
const SAMPLE_PRODUCTS = [
{ sku: 'boots-01', name: 'Hiking Boots', price: 89.99, imageUrl: 'https://picsum.photos/seed/1/300' },
{ sku: 'backpack-01', name: 'Trekking Backpack', price: 59.5, imageUrl: 'https://picsum.photos/seed/2/300' },
{ sku: 'jacket-01', name: 'Rain Jacket', price: 74.0, imageUrl: 'https://picsum.photos/seed/3/300' },
];
function App() {
const [query, setQuery] = useState('');
const searchInputRef = useRef(null);
useEffect(() => {
document.title = `Product Catalog (${SAMPLE_PRODUCTS.length} products)`;
searchInputRef.current.focus();
}, []);
const filtered = SAMPLE_PRODUCTS.filter((product) =>
product.name.toLowerCase().includes(query.toLowerCase())
);
return (
<div className="app">
<h1>Product Catalog</h1>
<input
ref={searchInputRef}
type="text"
placeholder="Search products..."
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
<div className="product-grid">
{filtered.map((product) => (
<ProductCard
key={product.sku}
name={product.name}
price={product.price}
imageUrl={product.imageUrl}
onSelect={() => alert(product.name + ' tapped')}
/>
))}
</div>
</div>
);
}
export default App;value={{query}} + onChange={{(event) => setQuery(event.target.value)}} is what's called a "controlled" input field – the VALUE comes from React state (useState), while ref additionally gives access to the REAL DOM element to call .focus(). Both concepts don't conflict, they solve different problems – more on controlled fields in chapter 13.
Use case 2: remembering values without re-rendering
The second, less obvious use case: useRef can also serve as a "container" for any mutable value that persists across render passes – unlike useState, though, changing ref.current does NOT trigger a re-render:
function ClickCounter() {
const clickCountRef = useRef(0);
function handleClick() {
clickCountRef.current = clickCountRef.current + 1;
console.log('Clicked so far:', clickCountRef.current);
// No re-render! The display would NOT update,
// even if we showed {clickCountRef.current} in the JSX.
}
return <button onClick={handleClick}>Click me</button>;
}Achtung: This is EXACTLY why useRef is the wrong choice when a changed value should actually become visible on screen – that's what useState is for. Use useRef for values that should run "in the background" (timer IDs, previous values for comparison, DOM elements), but that are never directly displayed in JSX.