The useRef Hook

The useRef Hook provides a way to store a value that persists across renders without causing the component to re-render when that value changes. It is most commonly used for two purposes: directly accessing a DOM element, and storing a mutable value that should not trigger a re-render.

Think of a ref as a box that can hold a value. The box exists as long as the component exists, and the value inside can be changed at any time — but React does not watch this box the way it watches state.

Importing useRef


import { useRef } from 'react';

Basic Syntax


const myRef = useRef(initialValue);

useRef returns an object with a single property: current. The current property holds the stored value and can be read or updated at any time:


const countRef = useRef(0);
console.log(countRef.current); // 0
countRef.current = 5;
console.log(countRef.current); // 5

Unlike state, changing ref.current does not cause a re-render.

Use Case 1 — Accessing a DOM Element

The most common use of useRef is to get a direct reference to an HTML element — similar to using document.getElementById() in plain JavaScript.

This is useful for focusing an input field, playing/pausing a video, scrolling to a specific element, or measuring element dimensions.


import { useRef } from 'react';

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

  function handleFocus() {
    inputRef.current.focus(); // Directly calls .focus() on the input element
  }

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="Click the button to focus" />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
}

The ref={inputRef} attribute on the input element tells React: "Assign this DOM element to inputRef.current." After the component renders, inputRef.current holds a direct reference to the actual input element in the browser.

Use Case 2 — Storing a Value Without Triggering Re-renders

Sometimes a value needs to be stored that does not affect the UI — for example, a timer ID, a previous state value, or a flag. Using state for this would cause unnecessary re-renders. A ref stores the value without any performance cost.


import { useState, useRef } from 'react';

function StopwatchTimer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);
  const intervalRef = useRef(null); // Stores the interval ID

  function startTimer() {
    if (isRunning) return;
    setIsRunning(true);
    intervalRef.current = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);
  }

  function stopTimer() {
    clearInterval(intervalRef.current);
    setIsRunning(false);
  }

  function resetTimer() {
    clearInterval(intervalRef.current);
    setIsRunning(false);
    setSeconds(0);
  }

  return (
    <div>
      <p>Time: {seconds}s</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
      <button onClick={resetTimer}>Reset</button>
    </div>
  );
}

The interval ID is stored in intervalRef.current. It can be accessed at any time to clear the interval, and changing it does not cause any unnecessary re-renders.

Use Case 3 — Tracking the Previous Value of State

A ref can capture the previous value of a state variable — something state alone cannot do:


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

function PreviousValueTracker() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef(0);

  useEffect(() => {
    prevCountRef.current = count; // Save current value after render
  });

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

After each render, the useEffect updates the ref with the current count. On the next render, the ref still holds the previous value from before the update — giving access to both the current and previous state values simultaneously.

useRef vs useState — When to Use Each

  • Use useState when a value change should update the UI. React watches state and re-renders the component when it changes.
  • Use useRef when a value needs to be stored across renders but changing it should NOT trigger a re-render. Examples: DOM references, timer IDs, previous values, form focus control.

Important — Do Not Read or Write Refs During Rendering

Refs should only be read or modified inside event handlers, useEffect, or other callbacks — not directly during the component's render. Reading a ref during render is generally fine, but modifying it during render can cause unpredictable behavior.

Key Points

  • useRef returns an object with a current property that persists across renders.
  • Changing ref.current does not cause a re-render.
  • The most common use is attaching ref to a DOM element to access it directly.
  • Refs are also used to store values like timer IDs that should not affect the UI.
  • Use useState for values that drive the UI, and useRef for values that do not.

The next topic introduces the useContext Hook — a way to share data across multiple components without passing props through every level of the component tree.

Leave a Comment

Your email address will not be published. Required fields are marked *