Understanding useRef in React: Concepts, Use Cases, and Examples

Understanding useRef in React (Beginner Friendly Guide)

React provides several hooks to manage state and behavior in functional components. One of the most commonly misunderstood hooks is useRef. While it looks simple, it solves important problems related to rendering and DOM interaction.

This blog explains useRef with clear concepts, practical examples, and when to use it correctly.

What is useRef?

useRef is a React Hook that returns a mutable object with a .current property.

const ref = useRef(initialValue);

This object persists for the entire lifetime of the component.

Key Characteristics of useRef

  • Stores a value that does not trigger re-render
  • Keeps the same reference across renders
  • Can directly access DOM elements
  • Works like a container that holds mutable data

How useRef Works Internally

When you write:

const myRef = useRef(10);

React internally creates an object like:

{
  current: 10
}

Important points:

  • This object is not recreated on every render
  • Only the .current value changes
  • React does not track changes inside .current

Why useRef is Needed

React components re-render when state or props change. However, not all data changes should trigger a re-render.

There are two main problems useRef solves:

1. Avoid unnecessary re-renders

Using useState for every value can cause performance issues.

2. Direct DOM manipulation

React is declarative, but some cases require imperative control (like focusing an input).

Accessing DOM Elements

import { useEffect, useRef } from "react";

function App() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} />;
}

Explanation:

  • ref attribute connects the DOM element to inputRef
  • inputRef.current gives direct access to the DOM node
  • The focus method is called after render

Persisting Values Without Re-render

import { useRef } from "react";

function App() {
  const countRef = useRef(0);

  const handleClick = () => {
    countRef.current += 1;
    console.log(countRef.current);
  };

  return <button onClick={handleClick}>Click</button>;
}

Here:

  • Value updates
  • Component does not re-render
  • Useful for storing non-UI data

Tracking Previous State

import { useEffect, useRef, useState } from "react";

function App() {
  const [count, setCount] = useState(0);
  const prevCount = useRef();

  useEffect(() => {
    prevCount.current = count;
  }, [count]);

  return (
    <>
      <p>Current: {count}</p>
      <p>Previous: {prevCount.current}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </>
  );
}

This pattern is useful when comparing previous and current values.

Working with Timers

import { useRef } from "react";

function App() {
  const timerRef = useRef(null);

  const start = () => {
    timerRef.current = setInterval(() => {
      console.log("Running...");
    }, 1000);
  };

  const stop = () => {
    clearInterval(timerRef.current);
  };

  return (
    <>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </>
  );
}

useRef helps store the interval ID across renders.

useRef vs useState (Conceptual Difference)

Concept useState useRef
Triggers re-render Yes No
Data visibility Used in UI Not directly used in UI
Mutability Immutable updates required Mutable (.current can change)
Lifecycle behavior Re-initialized logically Persisted across renders

When to Use useRef

Use useRef when:

  • You need direct access to a DOM element
  • You want to store mutable data without re-render
  • You need to persist values between renders
  • You are working with timers, intervals, or external libraries

When Not to Use useRef

Avoid using useRef when:

  • The value should be visible in UI
  • UI must update when value changes
  • State management is required

In these cases, useState is the correct choice.

Common Mistakes

1. Forgetting .current

inputRef.focus(); // incorrect

Correct:

inputRef.current.focus();

2. Using useRef for UI updates

Changing .current will not update the UI.

3. Expecting reactivity

useRef is not reactive like state.

Mental Model

  • useState → reactive data (affects UI)
  • useRef → persistent container (does not affect UI)