React useRef Hook

Accessing DOM elements and persisting values

🎯 What is useRef?

useRef is a React Hook that lets you reference a value or DOM element that persists across renders without causing re-renders. It's perfect for accessing DOM elements directly and storing mutable values.


import { useRef } from 'react';

function Component() {
  const inputRef = useRef(null);
  return <input ref={inputRef} />;
}
                                    

useRef Key Concepts

🎯

DOM Access

Reference DOM elements directly

const ref = useRef(null);
<div ref={ref}></div>
💾

Persist Values

Store values between renders

const count = useRef(0);
count.current += 1;
🚫

No Re-render

Changes don't trigger re-renders

// Updating ref doesn't re-render
ref.current = newValue;
🔧

Mutable Object

Modify .current property freely

ref.current.focus();
ref.current.value = 'text';

🔹 Accessing DOM Elements

The most common use of useRef is to access DOM elements directly. This allows you to call DOM methods like focus(), scrollIntoView(), or manipulate element properties imperatively.

import { useRef } from 'react';

function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    // Access DOM element and call focus()
    inputRef.current.focus();
  };
  
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>
        Focus Input
      </button>
    </div>
  );
}

Output:

🔹 Storing Previous Values

useRef can store values that persist between renders without causing re-renders. This is useful for keeping track of previous state values or storing timers and intervals.

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

function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();
  
  useEffect(() => {
    // Store current count as previous
    prevCountRef.current = count;
  });
  
  return (
    <div>
      <p>Current: {count}</p>
      <p>Previous: {prevCountRef.current}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Output:

Current: 0

Previous: undefined

🔹 Managing Timers

useRef is perfect for storing timer IDs because the reference persists across renders and you can clear the timer from anywhere in your component without causing re-renders.

import { useState, useRef } from 'react';

function Stopwatch() {
  const [time, setTime] = useState(0);
  const intervalRef = useRef(null);
  
  const start = () => {
    if (!intervalRef.current) {
      intervalRef.current = setInterval(() => {
        setTime(t => t + 1);
      }, 1000);
    }
  };
  
  const stop = () => {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
  };
  
  return (
    <div>
      <p>Time: {time}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

Output:

Time: 0s

🔹 Video Player Control

useRef is essential for controlling media elements like video and audio. You can access the element's methods to play, pause, or adjust playback without managing state.

import { useRef } from 'react';

function VideoPlayer() {
  const videoRef = useRef(null);
  
  const play = () => {
    videoRef.current.play();
  };
  
  const pause = () => {
    videoRef.current.pause();
  };
  
  return (
    <div>
      <video ref={videoRef} width="300">
        <source src="video.mp4" type="video/mp4" />
      </video>
      <div>
        <button onClick={play}>Play</button>
        <button onClick={pause}>Pause</button>
      </div>
    </div>
  );
}

Output:

Video Player

🔹 Counting Renders

You can use useRef to count how many times a component has rendered without triggering additional renders. This is useful for debugging and performance monitoring.

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

function RenderCounter() {
  const [count, setCount] = useState(0);
  const renderCount = useRef(0);
  
  useEffect(() => {
    renderCount.current += 1;
  });
  
  return (
    <div>
      <p>Count: {count}</p>
      <p>Renders: {renderCount.current}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

Output:

Count: 0

Renders: 1

🔹 useRef vs useState

useRef:

  • No re-render: Changing ref.current doesn't trigger re-render
  • Mutable: Can update ref.current directly
  • Synchronous: Changes are immediate
  • Use for: DOM access, timers, previous values

useState:

  • Triggers re-render: Updating state causes re-render
  • Immutable: Must use setter function
  • Asynchronous: State updates are batched
  • Use for: UI data that affects rendering

🔹 useRef Best Practices

✅ Do's:

  • DOM manipulation: Use for accessing DOM elements
  • Store timers: Keep interval/timeout IDs in refs
  • Previous values: Track previous state or props
  • Instance variables: Store values that don't affect render

❌ Don'ts:

  • Don't use for UI state: Use useState instead
  • Don't read during render: Refs are for side effects
  • Don't overuse: Not a replacement for state
  • Don't forget cleanup: Clear timers in useEffect cleanup

🧠 Test Your Knowledge

Does updating a ref trigger a re-render?