useRef in React

Using useRef in React — Not Just for DOM Access

If you are landing on this blog post, check out the previous ones where we talk about introduction to React Hooks, mastering useState in React and understanding useEffect in React. The useRef hook in React is often misunderstood, but it’s more powerful than many beginners realize. In this tutorial, we’ll use useRef to auto-focus our Task Tracker App’s input field and explain how it can also be used to store values across renders without causing re-renders. By the end, you’ll see why useRef is a must-know hook.

So, let’s dive in!

What is useRef?

The useRef hook returns a mutable object that stays the same across renders.

const ref = useRef(initialValue);
  • ref.current → the actual value stored
  • Updating ref.current does not trigger a re-render

This makes useRef perfect for:

  1. Accessing DOM elements (like focusing an input or scrolling to a section)
  2. Storing mutable values (like a timer ID or previous state)

What You’ll Learn in This Post

  • How useRef differs from useState
  • Two powerful use cases:
    1. Accessing and controlling DOM elements
    2. Storing mutable values without re-renders
  • Updating our Task Tracker App so the input auto-focuses

Example 1: Focusing an Input

import React, { useRef, useEffect } from "react";
function FocusInput() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // Focus input on mount
  }, []);

  return <input ref={inputRef} type="text" placeholder="Type here..." />;
}
export default FocusInput;

When the component mounts, the input automatically gets focus.

Example 2: Storing Mutable Values

import React, { useState, useRef } from "react";
function Timer() {
  const [count, setCount] = useState(0);
  const timerRef = useRef(null);

  const startTimer = () => {
    if (!timerRef.current) {
      timerRef.current = setInterval(() => setCount((c) => c + 1), 1000);
    }
  };

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

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

export default Timer;

useRef stores the timer ID between renders without triggering re-renders.

Mini Project Step: Auto-Focus Input in Task Tracker

Now let’s enhance our Task Tracker App so the input field auto-focuses when the app loads.

Updated App.jsx

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

function App() {
  const [tasks, setTasks] = useState([]);
  const [newTask, setNewTask] = useState("");
  const inputRef = useRef(null);

  // Fetch tasks when component mounts
  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/todos?_limit=5")
      .then((res) => res.json())
      .then((data) => setTasks(data))
      .catch((err) => console.error("Error fetching tasks:", err));
  }, []);

  // Auto-focus the input on mount
  useEffect(() => {
    inputRef.current.focus();
  }, []);

  const addTask = (e) => {
    e.preventDefault();
    if (!newTask.trim()) return;

    const task = { id: Date.now(), title: newTask };
    setTasks([...tasks, task]);
    setNewTask("");
    inputRef.current.focus(); // Re-focus after adding
  };

  return (
    <div style={{ padding: "20px" }}>
      <h1>Task Tracker</h1>

      {/* Form to add tasks */}
      <form onSubmit={addTask}>
        <input
          ref={inputRef}
          type="text"
          placeholder="Enter new task"
          value={newTask}
          onChange={(e) => setNewTask(e.target.value)}
        />
        <button type="submit">Add Task</button>
      </form>

      {/* Render task list */}
      <ul>
        {tasks.map((task) => (
          <li key={task.id}>{task.title || task.text}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

Now when the app loads, the input is automatically focused. After adding a task, focus goes back to the input for fast task entry.

Common Pitfalls with useRef

  1. Thinking useRef updates cause re-renders
    • Nope! Unlike useState, changing ref.current won’t trigger a re-render.
  2. Confusing with state
    • Use useState when UI needs to update.
    • Use useRef when you just need to hold a value across renders.

What We’ve Achieved

  • Learned the two main use cases for useRef
  • Enhanced our Task Tracker with auto-focus for better UX
  • Prepared for writing custom hooks in the next post

Learning Path

Post 1: Introduction to React Hooks (Why They Matter + Starter Project) – Read Post-1

Learn what hooks are, why they replaced class components, and set up the starter Task Tracker App.

Post 2: Mastering useState in React – Read Post 2

Manage state in functional components. Add new tasks dynamically with useState.

Post 3: Understanding useEffect (Side Effects + API Calls) – Read Post 3

Fetch tasks from an API, mimic lifecycle methods, and clean up side effects.

Post 4: Using useRef in React (Not Just for DOM Access) – Current Post

Control focus on the input field and learn how to store values without re-renders.

Post 5: Creating Custom Hooks (useLocalStorage) – Read Post 5

Extract reusable logic into a custom hook that persists tasks across refreshes.

Post 6: Optimizing Performance with useMemo and useCallbackRead Post 6

Add a search feature and optimize it with memoization to prevent unnecessary re-renders.

Post 7 (Final): Building a Complete Task Tracker with React Hooks – Read Post 7

Add delete + toggle complete functionality, and wrap up with a fully functional app.


Discover more from TACETRA

Subscribe to get the latest posts sent to your email.

Let's have a discussion!

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from TACETRA

Subscribe now to keep reading and get access to the full archive.

Continue reading