React Hooks - useRef
useRef is one of React's hooks. It is mainly used for:
- directly accessing DOM elements, or
- storing values that are not related to rendering
Unlike useState, changing the value of a ref does not cause the component to re-render.
Definition and Basic Syntax of useRef
useRef lets you persist a value across renders without triggering re-renders when the value changes, and it is commonly used to reference DOM elements.
javascript
import { useRef } from "react";
// Most common pattern: initialize with null for DOM refs
const ref = useRef(initialValue);
Why initialize with null?
- DOM elements are
nullbefore they are rendered → safe access - Clearly communicates "this will eventually hold a DOM node"
- Follows React's official convention for refs
- Makes the code more predictable and self-documenting
Common Use Cases with Examples
1. Accessing DOM Elements
The most frequent use case: focusing an input, measuring size, scrolling, etc.
javascript
import { useRef } from "react";
function FocusInput() {
const inputRef = useRef < HTMLInputElement > null;
const handleFocus = () => {
console.log("Before focus:", inputRef.current);
inputRef.current?.focus();
console.log("After focus:", inputRef.current);
};
return (
<div>
<input ref={inputRef} type="text" placeholder="I'll get focus!" />
<button onClick={handleFocus}>Focus</button>
</div>
);
}
Console output example after clicking the button:
javascript
Before focus: <input type="text" placeholder="I'll get focus!">
After focus: <input type="text" placeholder="I'll get focus!" autofocus>
2. Storing values that should not trigger re-renders
Very useful for:
- previous values
- timers / intervals
- last submitted value
- render counters
- flags (e.g. "isMounted")
Example: Prevent duplicate form submissions
javascript
import { useRef, useState } from "react";
function PreventDuplicateSubmission() {
const [inputValue, setInputValue] = useState("");
const lastSubmittedValue = useRef < string > "";
const handleSubmit = () => {
if (inputValue === lastSubmittedValue.current) {
alert("Duplicate values cannot be submitted.");
return;
}
// Submit the new value
lastSubmittedValue.current = inputValue;
console.log("Submitted value:", inputValue);
setInputValue(""); // clear input
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Enter a value"
/>
<button onClick={handleSubmit}>Submit</button>
<p>Last submitted value: {lastSubmittedValue.current}</p>
</div>
);
}
Why "" instead of null here?
Using null would require extra null checks:
javascript
if (lastSubmittedValue.current === null || inputValue === lastSubmittedValue.current)
Starting with an empty string is often cleaner in this specific case.
3. Tracking previous state / previous value
A very common pattern:
javascript
import { useEffect, useRef, useState } from "react";
function PreviousValueTracker() {
const [count, setCount] = useState(0);
const prevCount = useRef(0);
useEffect(() => {
prevCount.current = count; // store current value as "previous" for next render
}, [count]);
return (
<div>
<p>Current value: {count}</p>
<p>Previous value: {prevCount.current}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Key Characteristics of useRef
| Feature | Description |
|---|---|
| No re-render on change | Changing .current does not cause a re-render (unlike useState) |
| DOM access | Attach to elements via ref={myRef} → myRef.current gives the DOM node |
| Persists across renders | Value survives re-renders and lives as long as the component stays mounted |
| Mutable | You can freely mutate .current — that's its main purpose |
useRef vs useState – Quick Comparison
| Feature | useRef | useState |
|---|---|---|
| Initial value | useRef(initialValue) | useState(initialValue) |
| Triggers re-render? | No | Yes |
| Main purpose | DOM refs, non-rendering values, timers | UI state, values that affect rendering |
| Typical usage | .current mutation, previous values | State + setState, controlled components |
| Persists value | Yes (across renders) | Yes (but changing it causes re-render) |
Related Posts in Series
Collapse- 1. React Hooks - useRef
- 2. The Difference Between Debounce and Throttle and How to Use Them