January 2, 2025
O. Wolfson
One of the most common uses of useRef is to interact directly with DOM elements.
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 a ref object (inputRef) that connects directly to the DOM element.inputRef.current.focus() programmatically focuses the input, solving the issue.Another common use of useRef is to store the previous value of a state or prop without causing a re-render.
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.
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>
);
}
;
How It Fixes the Issue:
useRef persists the previous value across renders (previousCountRef.current).previousCount value correctly reflects the value before the latest render.useRef is valuable for storing values that need to persist across renders without causing a re-render.
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.stopTimer function reliably clears the timeout without causing re-renders.Another common use case for useRef is to prevent the re-creation of functions or objects between renders, improving performance.
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.useRef and ClosuresuseRef is also valuable when working with closures. By using useRef, you can avoid the problem of stale closures in asynchronous callbacks.
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;
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.setInterval callback always references the up-to-date countRef.current.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.