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)
Focus Input
jsx Copy
function InputWithoutRef ( ) {
const [inputValue, setInputValue] = useState ("" );
const focusInput = ( ) => {
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)
Focus Input
jsx Copy
function InputWithRef ( ) {
const inputRef = useRef (null );
const focusInput = ( ) => {
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.
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)
Current State: 0
Previous State: 0
Increment
jsx Copy
"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)
Current State: 0
Previous State: N/A
Increment
jsx Copy
"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 );
useEffect (() => {
previousCountRef.current = 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: {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
jsx Copy
import { useState, useRef } from "react" ;
function TimerWithRef ( ) {
const [count, setCount] = useState (0 );
const timeoutIdRef = useRef (null );
const startTimer = ( ) => {
timeoutIdRef.current = setTimeout (() => {
setCount ((prev ) => prev + 1 );
}, 1000 );
};
const stopTimer = ( ) => {
clearTimeout (timeoutIdRef.current );
};
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
jsx Copy
import { 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
)
jsx Copy
import { useState, useEffect } from "react" ;
function TimerWithoutRef ( ) {
const [count, setCount] = useState (0 );
useEffect (() => {
const intervalId = setInterval (() => {
console .log ("Count:" , count);
setCount (count + 1 );
}, 1000 );
return () => clearInterval (intervalId);
}, []);
return <p > Count: {count}</p > ;
}
export default TimerWithoutRef ;
With useRef
(Issue Resolved)
jsx Copy
import { useState, useRef, useEffect } from "react" ;
function TimerWithRef ( ) {
const [count, setCount] = useState (0 );
const countRef = useRef (count);
useEffect (() => {
countRef.current = count;
}, [count]);
useEffect (() => {
const intervalId = setInterval (() => {
console .log ("Count:" , countRef.current );
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-date countRef.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.