React useRef Hook
Accessing DOM elements and persisting values
🎯 What is useRef?
useRef is a React Hook that lets you reference a value or DOM element that persists across renders without causing re-renders. It's perfect for accessing DOM elements directly and storing mutable values.
import { useRef } from 'react';
function Component() {
const inputRef = useRef(null);
return <input ref={inputRef} />;
}
useRef Key Concepts
DOM Access
Reference DOM elements directly
const ref = useRef(null);
<div ref={ref}></div>
Persist Values
Store values between renders
const count = useRef(0);
count.current += 1;
No Re-render
Changes don't trigger re-renders
// Updating ref doesn't re-render
ref.current = newValue;
Mutable Object
Modify .current property freely
ref.current.focus();
ref.current.value = 'text';
🔹 Accessing DOM Elements
The most common use of useRef is to access DOM elements directly. This allows you to call DOM methods like focus(), scrollIntoView(), or manipulate element properties imperatively.
import { useRef } from 'react';
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
// Access DOM element and call focus()
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>
Focus Input
</button>
</div>
);
}
Output:
🔹 Storing Previous Values
useRef can store values that persist between renders without causing re-renders. This is useful for keeping track of previous state values or storing timers and intervals.
import { useState, useRef, useEffect } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
// Store current count as previous
prevCountRef.current = count;
});
return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCountRef.current}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Output:
Current: 0
Previous: undefined
🔹 Managing Timers
useRef is perfect for storing timer IDs because the reference persists across renders and you can clear the timer from anywhere in your component without causing re-renders.
import { useState, useRef } from 'react';
function Stopwatch() {
const [time, setTime] = useState(0);
const intervalRef = useRef(null);
const start = () => {
if (!intervalRef.current) {
intervalRef.current = setInterval(() => {
setTime(t => t + 1);
}, 1000);
}
};
const stop = () => {
clearInterval(intervalRef.current);
intervalRef.current = null;
};
return (
<div>
<p>Time: {time}s</p>
<button onClick={start}>Start</button>
<button onClick={stop}>Stop</button>
</div>
);
}
Output:
Time: 0s
🔹 Video Player Control
useRef is essential for controlling media elements like video and audio. You can access the element's methods to play, pause, or adjust playback without managing state.
import { useRef } from 'react';
function VideoPlayer() {
const videoRef = useRef(null);
const play = () => {
videoRef.current.play();
};
const pause = () => {
videoRef.current.pause();
};
return (
<div>
<video ref={videoRef} width="300">
<source src="video.mp4" type="video/mp4" />
</video>
<div>
<button onClick={play}>Play</button>
<button onClick={pause}>Pause</button>
</div>
</div>
);
}
Output:
🔹 Counting Renders
You can use useRef to count how many times a component has rendered without triggering additional renders. This is useful for debugging and performance monitoring.
import { useState, useRef, useEffect } from 'react';
function RenderCounter() {
const [count, setCount] = useState(0);
const renderCount = useRef(0);
useEffect(() => {
renderCount.current += 1;
});
return (
<div>
<p>Count: {count}</p>
<p>Renders: {renderCount.current}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Output:
Count: 0
Renders: 1
🔹 useRef vs useState
useRef:
- No re-render: Changing ref.current doesn't trigger re-render
- Mutable: Can update ref.current directly
- Synchronous: Changes are immediate
- Use for: DOM access, timers, previous values
useState:
- Triggers re-render: Updating state causes re-render
- Immutable: Must use setter function
- Asynchronous: State updates are batched
- Use for: UI data that affects rendering
🔹 useRef Best Practices
✅ Do's:
- DOM manipulation: Use for accessing DOM elements
- Store timers: Keep interval/timeout IDs in refs
- Previous values: Track previous state or props
- Instance variables: Store values that don't affect render
❌ Don'ts:
- Don't use for UI state: Use useState instead
- Don't read during render: Refs are for side effects
- Don't overuse: Not a replacement for state
- Don't forget cleanup: Clear timers in useEffect cleanup