2025-01-02 Web Development, Programming
Practical React UseRef Examples
By O. Wolfson
Example 1: Accessing DOM Elements
One of the most common uses of useRef
is to interact directly with DOM elements.
Without useRef
(Issue: Cannot Focus Input)
jsxfunction InputWithoutRef() {
const [inputValue, setInputValue] = useState("");
const focusInput = () => {
// Issue: No way to programmatically focus the input
console.log("Cannot focus the input without a reference!");
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={focusInput}>Focus Input</button>
</div>
);
}
export default InputWithoutRef;
Problem: There's no way to programmatically focus the input because React's state and props don't provide direct access to the DOM.
With useRef
(Issue Resolved)
jsx
function InputWithRef() {
const inputRef = useRef(null); // Create a ref for the input element
const focusInput = () => {
// Use the ref to focus the input
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
); }
export default InputWithRef;
How It Fixes the Issue:
useRef
provides aref
object (inputRef
) that connects directly to the DOM element.- Calling
inputRef.current.focus()
programmatically focuses the input, solving the issue.
Example 2: Tracking Previous State
Another common use of useRef
is to store the previous value of a state or prop without causing a re-render.
Without useRef
(Issue: Cannot Track Previous State)
jsx"use client";
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
function CounterWithoutRef() {
const [count, setCount] = useState(0);
const [previousCount, setPreviousCount] = useState(count);
useEffect(() => {
setPreviousCount(count);
}, [count]);
return (
<div className="flex flex-col items-center gap-4 justify-center p-10">
<div className="flex flex-col items-center gap-4 justify-center p-10 border border-gray-200 rounded-md">
<div className="text-2xl font-bold text-center flex flex-col gap-2">
<div>Current State: {count}</div>
<div>Previous State: {previousCount ?? "N/A"}</div>
</div>
<Button
type="button"
onClick={() => setCount(count + 1)}
className="text-lg"
>
Increment
</Button>
</div>
</div>
);
}
export default CounterWithoutRef;
Problem:
The previousCount
variable resets on every render because it doesn’t persist across renders.
With useRef
(Issue Resolved)
jsx"use client";
import { useState, useRef, useEffect } from "react";
import { Button } from "@/components/ui/button";
function CounterWithRef() {
const [count, setCount] = useState(0);
const previousCountRef = (useRef < number) | (null > null); // Initialize a ref to store the previous count
useEffect(() => {
previousCountRef.current = count; // Update the ref after every render
}, [count]);
return (
<div className="flex flex-col items-center gap-4 justify-center p-10">
<div className="flex flex-col items-center gap-4 justify-center p-10 border border-gray-200 rounded-md">
<div className="text-2xl font-bold text-center flex flex-col gap-2">
<div>Current State: {count}</div>
<div>Previous State: {previousCountRef.current ?? "N/A"}</div>
</div>
<Button
type="button"
onClick={() => setCount(count + 1)}
className="text-lg"
>
Increment
</Button>
</div>
</div>
);
}
export default CounterWithRef;
How It Fixes the Issue:
useRef
persists the previous value across renders (previousCountRef.current
).- The
previousCount
value correctly reflects the value before the latest render.
Example 3: Storing Mutable Values Across Renders
useRef
is valuable for storing values that need to persist across renders without causing a re-render.
Example: Storing a Timeout ID
jsximport { useState, useRef } from "react";
function TimerWithRef() {
const [count, setCount] = useState(0);
const timeoutIdRef = useRef(null); // Store the timeout ID
const startTimer = () => {
timeoutIdRef.current = setTimeout(() => {
setCount((prev) => prev + 1);
}, 1000);
};
const stopTimer = () => {
clearTimeout(timeoutIdRef.current); // Clears the active timeout reliably
};
return (
<div>
<p>Count: {count}</p>
<button onClick={startTimer}>Start Timer</button>
<button onClick={stopTimer}>Stop Timer</button>
</div>
);
}
export default TimerWithRef;
Why Useful:
useRef
ensures the timeout ID is stored persistently across renders.- The
stopTimer
function reliably clears the timeout without causing re-renders.
Example 4: Stabilizing Function References
Another common use case for useRef
is to prevent the re-creation of functions or objects between renders, improving performance.
Example: Stabilizing a Callback Function
jsximport { useState, useRef, useEffect } from "react";
function MouseTrackerWithRef() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMoveRef = useRef();
useEffect(() => {
handleMouseMoveRef.current = (event) => {
setPosition({ x: event.clientX, y: event.clientY });
};
});
useEffect(() => {
const mouseMoveHandler = (event) => handleMouseMoveRef.current(event);
window.addEventListener("mousemove", mouseMoveHandler);
return () => window.removeEventListener("mousemove", mouseMoveHandler);
}, []);
return (
<p>
Mouse position: {position.x}, {position.y}
</p>
);
}
export default MouseTrackerWithRef;
Why Useful:
useRef
allows the event listener to reference a stable function, avoiding unnecessary cleanup and re-attachment.- This improves performance and prevents memory leaks.
Example 5: Combining useRef
and Closures
useRef
is also valuable when working with closures. By using useRef
, you can avoid the problem of stale closures in asynchronous callbacks.
Without useRef
(Issue: Stale Closure in setInterval
)
jsximport { useState, useEffect } from "react";
function TimerWithoutRef() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
console.log("Count:", count); // Stale closure always sees initial count
setCount(count + 1);
}, 1000);
return () => clearInterval(intervalId);
}, []);
return <p>Count: {count}</p>;
}
export default TimerWithoutRef;
With useRef
(Issue Resolved)
jsximport { useState, useRef, useEffect } from "react";
function TimerWithRef() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count; // Update the ref whenever count changes
}, [count]);
useEffect(() => {
const intervalId = setInterval(() => {
console.log("Count:", countRef.current); // Access the latest count value
setCount(countRef.current + 1);
}, 1000);
return () => clearInterval(intervalId);
}, []);
return <p>Count: {count}</p>;
}
export default TimerWithRef;
How It Fixes the Issue:
useRef
provides access to the latest state value, avoiding the stale closure problem.- The
setInterval
callback always references the up-to-datecountRef.current
.
Conclusion
useRef
is a versatile hook in React, solving problems ranging from DOM manipulation to managing stateful values and stabilizing references. By understanding its various use cases, you can write more efficient and predictable React components. Whether it’s interacting with the DOM, tracking previous state, managing mutable values, stabilizing functions, or avoiding stale closures, useRef
is an indispensable tool for modern React development.