Understanding the useRef Hook in React

SoniyaSoniya
3 min read

React’s useRef is a powerful hook that helps manage references to DOM elements and persists mutable values across renders without causing re-renders. Let’s break it down simply.

useRef useRef is like a secret pocket in your React components—it lets you store things without making your component re-render. Let's break it down with real examples you can actually use.

1. The Basics: What is useRef?

useRef creates a box that can hold any value. Unlike useState, changing what's inside doesn't make your component re-render.

jsx

import { useRef } from 'react';

function SecretBox() {
  const myBox = useRef("initial value");

  console.log(myBox.current); // "initial value"

  return <div>Check the console!</div>;
}

2. Real Use #1: Grabbing DOM Elements

Instead of document.getElementById(), use useRef:

jsx

function AutoFocusInput() {
  const inputRef = useRef();

  // This runs when component loads
  useEffect(() => {
    inputRef.current.focus(); // Automatically focuses the input!
  }, []);

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

3. Real Use #2: Tracking Previous State

Need to remember what something was before it changed?

jsx

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

  useEffect(() => {
    prevCount.current = count; // Stores without re-render
  }, [count]);

  return (
    <div>
      <p>Now: {count}, Before: {prevCount.current}</p>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}

4. Real Use #3: Storing Timer IDs

Perfect for setTimeout/setInterval:

jsx

function Timer() {
  const timerId = useRef();

  const startTimer = () => {
    timerId.current = setInterval(() => {
      console.log("Tick!");
    }, 1000);
  };

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

  return (
    <div>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

Common Mistakes to Avoid

Mistake 1: Changing refs during render

jsx

function BadExample() {
  const myRef = useRef(0);
  myRef.current = 10; // ❌ Don't do this during render!
  return <div>{myRef.current}</div>;
}

Fix: Change refs in effects or handlers

jsx

function GoodExample() {
  const myRef = useRef(0);

  useEffect(() => {
    myRef.current = 10; // ✅ Safe!
  }, []);

  return <div>{myRef.current}</div>;
}

Mistake 2: Using refs when you need state

jsx

function Counter() {
  const count = useRef(0); // ❌ Bad - won't trigger updates

  return (
    <button onClick={() => count.current++}>
      {count.current} // This number won't change!
    </button>
  );
}

Fix: Use useState when UI needs to update

jsx

function Counter() {
  const [count, setCount] = useState(0); // ✅ Good

  return (
    <button onClick={() => setCount(c => c + 1)}>
      {count} // Now this works!
    </button>
  );
}

When Should You Actually Use useRef?

✔ To interact with DOM elements (focus, scroll, measurements)
✔ To store mutable values that shouldn't trigger re-renders
✔ To keep track of previous values
✔ To store timer IDs, animation frames, or other imperatives

Key Takeaways

  1. useRef is like a persistent storage box for your component

  2. Changing .current doesn't cause re-renders

  3. Great for DOM manipulation and storing "background" values

  4. Don't use it when you need UI updates (use useState instead)

Now go try these examples in your code! Which useRef trick will you use first? 🚀

20
Subscribe to my newsletter

Read articles from Soniya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Soniya
Soniya