Introduction
React provides a declarative way to build user interfaces. State and props are the core tools for handling dynamic data, but sometimes you need to interact directly with DOM elements or persist values across renders without triggering re-renders. This is where the useRef hook comes in.
The useRef hook is a versatile tool in React functional components. It can:
- Access and manipulate DOM elements.
- Persist values across renders without causing a re-render.
- Store previous values of state for comparison.
In this post, we will explore practical examples of using useRef for common use cases, including focusing input fields, storing previous state, and working with persistent values.
What is useRef?
useRef is a React hook that returns a mutable ref object. The object has a current property that can hold any value. Unlike state, changing a ref does not trigger a component re-render.
Syntax
const refContainer = useRef(initialValue);
refContainer: The ref object.refContainer.current: Stores the mutable value.initialValue: Optional initial value for the ref.
1. Accessing DOM Elements
One of the most common use cases for useRef is to access DOM elements directly. This is useful for focusing inputs, scrolling elements, or triggering animations.
Example: Focusing an Input Field
import React, { useRef } from "react";
function FocusInput() {
const inputRef = useRef(null);
const handleFocus = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" placeholder="Enter your name" />
<button onClick={handleFocus}>Focus Input</button>
</div>
);
}
export default FocusInput;
Explanation:
inputRefis attached to the input element viaref={inputRef}.- Clicking the button triggers
inputRef.current.focus(), focusing the input field. - This allows direct DOM manipulation in a controlled React environment.
Example: Scrolling to an Element
function ScrollToSection() {
const sectionRef = useRef(null);
const scrollToSection = () => {
sectionRef.current.scrollIntoView({ behavior: "smooth" });
};
return (
<div>
<button onClick={scrollToSection}>Go to Section</button>
<div style={{ height: "500px" }}></div>
<div ref={sectionRef} style={{ height: "200px", background: "lightblue" }}>
Scroll Target
</div>
</div>
);
}
- Ref can be used to scroll elements into view, ideal for smooth navigation.
2. Persisting Values Across Renders
Unlike state, updating a useRef value does not trigger a re-render. This makes it suitable for storing values that persist across renders but do not need to update the UI.
Example: Storing Interval IDs
import React, { useState, useRef, useEffect } from "react";
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
const startTimer = () => {
intervalRef.current = setInterval(() => {
setCount((prev) => prev + 1);
}, 1000);
};
const stopTimer = () => {
clearInterval(intervalRef.current);
};
return (
<div>
<h1>{count}</h1>
<button onClick={startTimer}>Start</button>
<button onClick={stopTimer}>Stop</button>
</div>
);
}
export default Timer;
Explanation:
intervalRefstores the interval ID.- Since
intervalRef.currentdoes not cause re-renders, it is safe to use for mutable values.
3. Example: Focusing Input Fields
Focusing input fields is a practical example often required in forms, search bars, and login screens.
function LoginForm() {
const usernameRef = useRef();
const passwordRef = useRef();
const handleSubmit = (e) => {
e.preventDefault();
console.log("Username:", usernameRef.current.value);
console.log("Password:", passwordRef.current.value);
usernameRef.current.value = "";
passwordRef.current.value = "";
usernameRef.current.focus();
};
return (
<form onSubmit={handleSubmit}>
<input ref={usernameRef} type="text" placeholder="Username" />
<input ref={passwordRef} type="password" placeholder="Password" />
<button type="submit">Login</button>
</form>
);
}
- Refs allow direct access to input values without binding state.
- Input is reset and focus is returned to the first field after submission.
4. Example: Storing Previous State
Sometimes it is useful to compare current state with previous state. useRef can store previous values without triggering re-renders.
import React, { useState, useRef, useEffect } from "react";
function PreviousStateExample() {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
}, [count]);
const prevCount = prevCountRef.current;
return (
<div>
<h1>Current Count: {count}</h1>
<h2>Previous Count: {prevCount}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Explanation:
prevCountRef.currentholds the previous value ofcount.useEffectupdates the ref after each render.- Ref persists the value across renders without causing additional re-renders.
5. Combining useRef with useEffect
useRef is often combined with useEffect for advanced use cases such as measuring DOM elements, triggering animations, or detecting first render.
Example: Detecting First Render
function FirstRenderExample() {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
console.log("This is the first render");
isFirstRender.current = false;
} else {
console.log("This is a re-render");
}
});
return <div>Hello World</div>;
}
isFirstRender.currentallows you to distinguish between the initial render and subsequent updates.
6. Example: Measuring DOM Elements
function MeasureDiv() {
const divRef = useRef();
const handleClick = () => {
const height = divRef.current.clientHeight;
console.log("Div height:", height);
};
return (
<div>
<div ref={divRef} style={{ height: "150px", background: "lightgreen" }}>
Measure Me
</div>
<button onClick={handleClick}>Get Height</button>
</div>
);
}
useRefstores a reference to a DOM element and allows measuring dimensions or positions.
7. useRef vs useState
| Feature | useRef | useState |
|---|---|---|
| Triggers re-render? | No | Yes |
| Stores mutable value | Yes | No |
| Common use cases | DOM refs, persistent values, timers | Component state, UI updates |
| Update pattern | Directly modify ref.current | Use setState function |
- Use
useRefwhen you need a mutable object that does not require re-rendering. - Use
useStatewhen updating a value should cause a re-render.
Best Practices
- Use refs for DOM manipulation or non-UI state.
- Avoid overusing refs; prefer declarative state when possible.
- Do not mutate state with refs; use refs for values that do not need re-rendering.
- Combine refs with hooks like
useEffectfor lifecycle-aware operations. - Name refs clearly to indicate their purpose (
inputRef,timerRef,prevStateRef).
Real-World Scenarios
- Form Input Autofocus – Automatically focus input fields for better UX.
- Timers and Intervals – Store interval IDs without causing unnecessary re-renders.
- Animations – Manipulate DOM elements for animations directly.
- Storing Previous State – Compare previous and current state in forms, counters, or analytics.
- Measuring DOM Elements – Get dimensions for dynamic layouts.
Leave a Reply