useRef Hook
useRef Hook — Working with the DOM and saving values without rerendering
The useRef hook in React has two main roles:
useRef returns an object in this format:
{ current: ... }React stores the reference in this current property.
const ref = useRef(initialValue);
This is the most famous and common example: accessing input via ref.
import React, { useRef } from 'react';
export default function InputFocus() {
const inputRef = useRef(null);
function handleFocus() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} placeholder="Write something..." />
<button onClick={handleFocus}>Focus</button>
</div>
);
}
In this example, `inputRef.current` references the input DOM element. By clicking the button, we programmatically focus the input.
If you want to store a variable that persists across re-renders but doesn't cause re-renders, use ref. This is very useful for storing timers, previous values, or metrics.
import React, { useRef, useState, useEffect } from 'react';
export default function RenderCounter() {
const [count, setCount] = useState(0);
const renders = useRef(0);
useEffect(() => {
renders.current = renders.current + 1;
});
return (
<div>
<p>Count: {count}</p>
<p>Render count: {renders.current}</p>
<button onClick={() => setCount(prev => prev + 1)}>Increment</button>
</div>
);
}
In this example, the `renders.current` value preserves the render count, but doesn't cause re-renders even when changed. If we did the same with state, it would cause infinite re-renders.
React only re-renders when state or props change. Ref is specifically designed to be a "persistent storage" without triggering re-renders. React simply remembers that value throughout the component's entire lifecycle.
This is a professional example: when you want to know what the previous state value was.
import React, { useState, useEffect, useRef } from 'react';
export default function PreviousValue() {
const [name, setName] = useState('');
const prevName = useRef('');
useEffect(() => {
prevName.current = name; // save previous value
}, [name]);
return (
<div>
<input value={name} onChange={e => setName(e.target.value)} />
<p>Current name: {name}</p>
<p>Previous name: {prevName.current}</p>
</div>
);
}
Every time name changes, we save the previous value in the ref. This is frequently used in form validation, comparisons, or animations.
If setInterval is created in an effect, ref can help preserve the latest callback without creating a new interval on every render.
import React, { useState, useEffect, useRef } from 'react';
export default function Timer() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(intervalRef.current);
}, []);
return (
<div>
<p>Elapsed time: {seconds} seconds</p>
<button onClick={() => clearInterval(intervalRef.current)}>Stop</button>
</div>
);
}
The ref here stores the interval ID so it's easy to stop it without changing state.
| useState | useRef |
|---|---|
| Changes cause re-renders. | Changes don't cause re-renders. |
| Stores data for rendering. | Stores data throughout component lifecycle. |
| Used for UI updates. | Used for DOM or internal data. |
Write a component that:
Use only one hook: useRef for all three purposes.