useRef — what it is, the ref hook and working with DOM in React

Author: IT Sectr Published: 2026-07-04 Reading time: 9 min

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 — a hook for creating a mutable ref object with a .current property.
  • No re-render — changing .current does not trigger a component re-render.
  • DOM references — passed to the element's ref prop for direct DOM node access.
  • Value storage — suitable for timers, previous values and any data between renders.
  • Stable reference — the ref object does not change between renders, unlike closures.

What is useRef in React

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.

jsx
import { useRef } from 'react';

function Component() {
    const countRef = useRef(0);
    // countRef.current = 0 initially
    // countRef.current = 5 after mutation
    // No re-render happens!
}

Accessing DOM elements with useRef

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.

jsx
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.

Storing mutable values without re-renders

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.

jsx
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></>;
}

useRef vs useState: when to use what

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.

CriterionuseRefuseState
Re-renderDoes not trigger on changeTriggers on every setState
MutationDirect: ref.current = valueVia setter: setState(value)
Usage in JSXNot used (no effect)Used in component output
ExampleTimers, DOM refs, previous valuesForm 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.

useRef in combination with useEffect

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.

jsx
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.

Common mistakes with useRef

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.

  • Ref in JSX — reading ref.current inside a render function leads to UI and data desynchronization.
  • Ref as a useEffect dependency — ref.current should not be in the dependency array; React does not track its changes.
  • Forgotten null check — on unmount ref.current becomes null; always check before access.
  • Storing functions in ref — update ref.current on every render if using the fresh callback pattern.
jsx
// ❌ 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

Can useRef be used to store the previous state?

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.

Why is ref.current null on the first render?

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.

What is the difference between useRef and createRef?

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.

Can useRef be passed through props?

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.

How to measure an element’s size with useRef?

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

  • useRef — a hook for creating a mutable ref object preserved between renders without triggering a re-render.
  • DOM access — the ref prop is passed to an element for direct DOM node access; available after mounting.
  • Data storage — timers, callbacks, flags and any values that do not affect the UI should be stored in ref.
  • useRef vs useState — choose useState if the change should re-render the UI; useRef if not.
  • Previous value pattern — store the previous value in ref, update in useEffect for comparison.
  • Ref in JSX — do not read ref.current in the render phase; this leads to UI and data desynchronization.
  • forwardRef — use it to pass a ref through props to a child component.

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.

Discuss the project

Read also