useRef is a React hook that creates a mutable ref object with a .current property, preserved between component renders. Unlike useState, changing .current does not trigger a re-render, making useRef ideal for storing DOM references, timers and any values that need to persist across renders without UI re-rendering. According to React Documentation (2025), the ref object is created once during the component's lifetime and does not change between renders, ensuring reference stability.
Key takeaways
useRef is a hook added in React 16.8 that returns a mutable ref object with a single .current property. The initial value is assigned to .current upon component mount. The main difference from a regular variable is that the ref object survives re-renders: on subsequent renders React does not recreate the ref but returns the same object.
Internally, useRef uses the same mechanism as useState and useEffect — a linked list of hooks on the fiber node. However, unlike useState, React does not track ref object changes, does not queue updates and does not schedule re-renders. This makes useRef an extremely lightweight hook with no performance impact even under frequent changes.
According to React Team — Hooks FAQ (2025), useRef is essentially a “box” that holds a value. Syntactic sugar: const ref = useRef(initialValue) is equivalent to const ref = { current: initialValue } with the difference that React guarantees the ref object will be the same on every render. No magic — just a stable reference managed by React.
import { useRef } from 'react';
function Component() {
const countRef = useRef(0);
// countRef.current = 0 initially
// countRef.current = 5 after mutation
// No re-render happens!
}
The most common use case for useRef is obtaining direct access to a DOM element. React assigns elementRef.current a reference to the DOM node after the component mounts. This is necessary for focusing an input, measuring element dimensions, integrating with animation libraries and libraries that do not use React rendering.
React automatically manages DOM ref references: on mount it assigns the actual DOM node, on unmount it nullifies it. This guarantees that .current always holds the up-to-date value and prevents memory leaks through dangling references to removed elements. No manual cleanup is required.
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// Focus input after component mounts
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
According to React — Refs and DOM documentation (2025), do not use ref for declarative operations — opening/closing modals, managing visibility. Use state and props for those purposes. Refs are intended for imperative operations that cannot be expressed declaratively: focus, text selection, integration with third-party libraries.
The second most important use case is storing arbitrary values that need to persist between renders but whose changes should not trigger a re-render. Typical examples: timer identifiers (setInterval/setTimeout), cancellation flags for requests, previous prop values for comparison and any data that does not affect UI output.
This is especially useful in useEffect: useRef stores the timer identifier and the cleanup clears it. If timerId were stored in useState, each setTimerId call would cause an unnecessary re-render that is neither needed for logic nor for UI. useRef solves this problem without overhead and without unnecessary re-renders.
function Timer() {
const intervalRef = useRef(null);
const start = () => {
intervalRef.current = setInterval(() => {
console.log('tick');
}, 1000);
};
const stop = () => {
clearInterval(intervalRef.current);
};
useEffect(() => stop, []);
return <><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}
The main dilemma — choosing between useRef and useState — is resolved by one question: “Is a re-render needed when the value changes?” If yes — useState. If no — useRef. useState stores state that affects component output; useRef stores data needed for internal logic but not affecting the UI.
In practice, developers often use useRef to store callbacks to avoid closure issues. For example, if useEffect subscribes to an event and the callback needs the current state — store the callback in useRef. On each render, update ref.current with a new function, and the effect will always invoke the fresh callback without re-subscribing.
| Criterion | useRef | useState |
|---|---|---|
| Re-render | Does not trigger on change | Triggers on every setState |
| Mutation | Direct: ref.current = value | Via setter: setState(value) |
| Usage in JSX | Not used (no effect) | Used in component output |
| Example | Timers, DOM refs, previous values | Form data, UI state, flags |
There is an anti-pattern: using useRef for data that is needed in JSX but whose changes should not cause a re-render. This leads to desynchronization — the UI shows old data while ref.current is already new. If the value is displayed in the UI — use useState. If only used internally — useRef.
The useRef + useEffect combination is the standard pattern for tracking previous prop values. Store the previous value in ref, compare with the current value in useEffect and make decisions based on the difference. This is particularly useful in animations when you need to know what the value was before the change.
function PriceDisplay({ price }) {
const prevPriceRef = useRef(price);
useEffect(() => {
const prevPrice = prevPriceRef.current;
if (price > prevPrice) {
animateUp();
} else if (price < prevPrice) {
animateDown();
}
prevPriceRef.current = price;
}, [price]);
return <span>${price}</span>;
}
According to React Documentation — Hooks FAQ (2025), this pattern is called the “previous value pattern”. It works because useRef preserves the value between renders, and useEffect runs after the changes are committed. First the DOM is updated with the new price, then useEffect compares it with the previous (still in ref) and updates ref.current to the current value.
The most frequent mistake is reading ref.current during the render phase to compute JSX. Since changing ref.current does not trigger a re-render, the component may use a stale value. If .current is involved in UI output — use useState. If you need to synchronize ref and state, use useEffect to update the state from ref.
// ❌ Don't read ref.current during render for display
function BadComponent() {
const valRef = useRef(0);
return <p>{valRef.current}</p>; // will NOT update on mutation
}
// ✅ Use state for display, ref for logic
function GoodComponent() {
const [val, setVal] = useState(0);
const valRef = useRef(0);
return <p>{val}</p>;
}
Another common mistake is using useRef as the only way to store state in a component when the data actually affects the UI. Developers choose useRef to avoid “unnecessary” re-renders, but end up with a UI that does not update. The correct approach: use useState for UI data and useRef only for auxiliary values not involved in rendering.
Frequently asked questions
Yes, this is a common pattern — the previous state pattern. Create a ref and update it in useEffect each time the tracked value changes. Between the render and the effect, ref.current holds the previous value, which can be compared with the current one. This does not require additional re-renders and works with any data type.
React assigns ref.current the DOM node value only after the element is rendered and added to the real DOM. During the first render the component is not yet mounted, so ref.current equals the initial value (null). DOM access via ref.current is only possible in useEffect or in event handlers that are called after mounting.
createRef creates a new ref object on every render — it should only be used in class components. useRef creates the ref once and returns the same object on all subsequent renders. In functional components use only useRef; createRef will cause value loss on re-render because the object will be recreated.
Yes, a ref object can be passed as a regular prop: <Child inputRef={inputRef} />. The child component uses it via inputRef.current. However, to pass a ref directly to a child component’s DOM element, use forwardRef — an HOC that allows forwarding refs through props. Without forwardRef, the ref will not be automatically attached to the DOM element inside the child component.
Attach a ref to the element, then in useEffect read ref.current.getBoundingClientRect() or ref.current.offsetWidth / offsetHeight. For reactive size change tracking, use ResizeObserver within useEffect: create an observer, subscribe to changes and update state with the new dimensions. Do not forget to disconnect the observer in the cleanup.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also