Back to roadmaps react Course

useRef and DOM Operations

Updating states in React triggers a component re-render. However, there are times you want to persist variables between renders or access actual DOM elements directly without triggering UI updates. For these cases, React provides the useRef hook.


1. Persisting Mutable Variables Across Renders

The useRef hook returns a mutable object with a single property: .current. The value stored inside .current persists across component renders, and updating it does not trigger a re-render.

import { useRef } from "react";

function ClickTracker() {
  // Initialize ref value to 0
  const clickCount = useRef(0);

  const handleClick = () => {
    clickCount.current += 1;
    // Updates value but does NOT trigger a re-render
    console.log("Total Clicks stored in ref:", clickCount.current);
  };

  return <button onClick={handleClick}>Click Me (Log Ref)</button>;
}

2. Direct DOM Access (Refs)

The most common use case for useRef is interacting with browser HTML elements directly (for example, managing input focus, scrolling containers, or computing element bounding boxes).

import { useRef } from "react";

function FocusInput() {
  // Create ref pointer
  const inputRef = useRef(null);

  const triggerFocus = () => {
    // Access and focus the DOM input element
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <input ref={inputRef} type="text" placeholder="Type here..." />
      <button onClick={triggerFocus}>Focus Input Box</button>
    </div>
  );
}

3. Creating a stopwatch with useRef

Here is a practical stopwatch component that uses useRef to store the interval handle. Because updating the interval pointer does not require a re-render, we store it in a ref:

import { useState, useRef } from "react";

function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  const timerId = useRef(null);

  const startTimer = () => {
    if (timerId.current !== null) return;
    
    timerId.current = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);
  };

  const stopTimer = () => {
    if (timerId.current !== null) {
      clearInterval(timerId.current);
      timerId.current = null;
    }
  };

  return (
    <div>
      <p>Elapsed Time: {seconds}s</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}
Published on Last updated: