React useRef Hook: DOM References and Mutable Values

Learn React useRef Hook: DOM References and Mutable Values with practical examples, explanations, best practices, and real-world coding techniques.

Introduction to useRef in React

The useRef Hook lets a React component retain a value between renders without triggering a re-render when that value changes. It is also commonly used to access DOM elements directly, such as focusing an input, scrolling to a section, or measuring an element.

Unlike useState, changing a ref does not ask React to render the component again. This makes refs useful for values that need to persist but do not directly determine what appears on the screen.

What Is a Ref?

A ref is a plain JavaScript object with a mutable current property. React returns the same ref object across renders of the same mounted component.

import { useRef } from 'react';

function Example() {
  const countRef = useRef(0);

  function increment() {
    countRef.current += 1;
    console.log(countRef.current);
  }

  return (
    <button onClick={increment}>
      Increment ref
    </button>
  );
}

Each click updates the stored value, but the displayed interface does not automatically update because changing countRef.current does not trigger a render.

Basic Syntax

const ref = useRef(initialValue);
  • initialValue is the value assigned to ref.current when the ref object is created.
  • ref is the persistent object returned by React.
  • ref.current stores the current mutable value.
  • Updating ref.current does not trigger a component re-render.

For a refresher on state-driven rendering, see React State.

useRef vs useState

Both Hooks retain information between renders, but they serve different purposes. Choose state when changing a value should update the interface. Choose a ref when a value must persist without causing a render.

Feature useState useRef
Persists between renders Yes Yes
Triggers a render when updated Yes, when React processes a state update No
Common purpose Data displayed in the UI DOM references and mutable values
Typical access State value and setter ref.current

Accessing DOM Elements with useRef

One of the most common uses of useRef is referencing a DOM element rendered by React. Pass the ref to an element's ref prop, and React assigns the corresponding DOM node to ref.current when the element is mounted.

import { useRef } from 'react';

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

  function focusInput() {
    inputRef.current?.focus();
  }

  return (
    <div>
      <input
        ref={inputRef}
        type="search"
        placeholder="Search topics"
      />
      <button onClick={focusInput}>
        Focus search
      </button>
    </div>
  );
}

export default SearchBox;

When the button is clicked, the input receives focus. The optional chaining operator (?.) prevents an error if the input is not currently available.

Scrolling to an Element

Refs can also be used to scroll to a particular element, such as a section in a long documentation page.

import { useRef } from 'react';

function DocumentationPage() {
  const sectionRef = useRef(null);

  function goToSection() {
    sectionRef.current?.scrollIntoView({
      behavior: 'smooth',
      block: 'start'
    });
  }

  return (
    <div>
      <button onClick={goToSection}>
        Go to advanced topics
      </button>

      <div style={{ height: '700px' }}>
        Introduction content
      </div>

      <section ref={sectionRef}>
        <h2>Advanced topics</h2>
        <p>Explore more advanced concepts here.</p>
      </section>
    </div>
  );
}

Calling scrollIntoView() on the referenced element scrolls it into view. This is useful for navigation within articles, forms, and dashboards.

Storing Values Between Renders

A ref can hold information such as a timer identifier, an external library instance, or a previous value. React preserves the ref object for the lifetime of the mounted component.

import { useRef } from 'react';

function RenderCounter() {
  const renderCount = useRef(0);

  renderCount.current += 1;

  return (
    <p>
      Render count: {renderCount.current}
    </p>
  );
}

This example demonstrates persistent mutable storage, but it is not a reliable pattern for displaying render counts. Updating a ref during rendering does not trigger another render, and rendering should remain free of side effects. Use an appropriate state-based approach when the count must be displayed and updated predictably.

Using useRef with Timers

Timer identifiers are another useful ref value. The identifier can be retained so the timer can be cleared when needed without introducing state updates.

import { useRef } from 'react';

function TimerControls() {
  const timerRef = useRef(null);

  function startTimer() {
    if (timerRef.current !== null) return;

    timerRef.current = setTimeout(() => {
      console.log('Timer completed');
      timerRef.current = null;
    }, 2000);
  }

  function cancelTimer() {
    if (timerRef.current !== null) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  }

  return (
    <div>
      <button onClick={startTimer}>Start timer</button>
      <button onClick={cancelTimer}>Cancel timer</button>
    </div>
  );
}

This example prevents multiple active timers and allows cancellation. For timers that must always be cleaned up when a component unmounts, add an Effect with a cleanup function. Learn more in React useEffect.

Tracking a Previous Value

A ref can preserve a value from an earlier render. An Effect can update that stored value after React commits the current render.

import { useEffect, useRef } from 'react';

function PriceDisplay({ price }) {
  const previousPrice = useRef(price);

  useEffect(() => {
    previousPrice.current = price;
  }, [price]);

  return (
    <div>
      <p>Current price: {price}</p>
      <p>Previous price: {previousPrice.current}</p>
    </div>
  );
}

The ref is updated after the render commits, so the displayed previous price represents the value stored before that update. This pattern is useful when comparing values, but avoid adding refs when ordinary state or derived data is sufficient.

Important Rules and Limitations

  • Call Hooks at the top level: Call useRef inside a function component or custom Hook, not inside loops, conditions, or nested functions.
  • Do not use refs for rendered data: Updating ref.current does not trigger a re-render. Use useState if the interface must react to a value changing.
  • Avoid reading or writing refs during rendering: Keep rendering pure. There are narrow exceptions for predictable initialization, but ordinary ref updates belong in event handlers or Effects.
  • Handle missing DOM nodes: A ref may be null before an element mounts or after it unmounts.
  • Remember component lifetime: A ref persists while its component remains mounted. It is not a permanent store across page reloads or remounts.
  • Do not overuse direct DOM manipulation: Prefer React props and state for normal UI updates. Use refs when an imperative operation is genuinely needed.

These rules follow the broader Rules of React Hooks and help prevent rendering bugs.

Common Mistakes

Expecting the UI to update after changing a ref

Assigning a new value to ref.current does not trigger a render. Use state when the updated value needs to appear in the interface.

Accessing a DOM node before it is mounted

A DOM ref is commonly null during the initial render. Access it in an event handler or an Effect after the element is mounted.

Using refs as a replacement for state

Refs are intended for mutable values that do not directly drive rendering. Storing visible application data in refs can leave the interface out of sync with the actual value.

Updating refs during rendering

Unconditional ref mutations during rendering can produce confusing behavior, especially when React performs additional renders. Prefer event handlers or Effects for updates.

When Should You Use useRef?

Use useRef when a component needs persistent mutable information or access to a DOM node without scheduling a re-render. Common examples include:

  • Focusing an input or controlling media playback.
  • Scrolling to a particular section or measuring an element.
  • Storing timer identifiers or external object instances.
  • Keeping track of a value between renders when the value itself does not determine the UI.

If the value should change what users see, prefer state. If you need to coordinate with external systems, consider whether useEffect is appropriate. For more complex state transitions, explore useReducer.

Key Takeaway

useRef provides a stable object whose current property can hold mutable data between renders. It is especially valuable for DOM access, timers, and imperative operations. Choose state for information that must update the interface, and use refs only when a re-render is not the intended result.

Code Examples

import { useRef } from "react";

function Example() {
  const countRef = useRef(0);

  function increment() {
    countRef.current += 1;
    console.log(countRef.current);
  }

  return <button onClick={increment}>Increment ref</button>;
}

export default Example;
    
import { useRef } from "react";

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

  function focusInput() {
    inputRef.current?.focus();
  }

  return (
    <div>
      <input ref={inputRef} type="search" placeholder="Search topics" />
      <button onClick={focusInput}>Focus search</button>
    </div>
  );
}

export default SearchBox;
    
import { useRef } from "react";

function DocumentationPage() {
  const sectionRef = useRef(null);

  function goToSection() {
    sectionRef.current?.scrollIntoView({
      behavior: "smooth",
      block: "start"
    });
  }

  return (
    <div>
      <button onClick={goToSection}>Go to advanced topics</button>
      <div style={{ height: "700px" }}>Introduction content</div>
      <section ref={sectionRef}>
        <h2>Advanced topics</h2>
        <p>Explore more advanced concepts here.</p>
      </section>
    </div>
  );
}

export default DocumentationPage;
    
import { useRef, useEffect } from "react";

function TimerControls() {
  const timerRef = useRef(null);

  useEffect(() => {
    return () => {
      if (timerRef.current !== null) {
        clearTimeout(timerRef.current);
      }
    };
  }, []);

  function startTimer() {
    if (timerRef.current !== null) return;

    timerRef.current = setTimeout(() => {
      console.log("Timer completed");
      timerRef.current = null;
    }, 2000);
  }

  function cancelTimer() {
    if (timerRef.current !== null) {
      clearTimeout(timerRef.current);
      timerRef.current = null;
    }
  }

  return (
    <div>
      <button onClick={startTimer}>Start timer</button>
      <button onClick={cancelTimer}>Cancel timer</button>
    </div>
  );
}

export default TimerControls;
    

Pro Tip

Use useRef for DOM access and mutable values that should survive renders without triggering another render. If changing a value should update the UI, use useState instead.

🎨 IgKnight Docs

Master HTML, CSS, JS, Python, .NET and more with easy-to-follow lessons and practical code examples.

© 2025 IgKnight Learning Platform. All rights reserved.