DEV Community

Cover image for Understanding React's `useRef` and Ref Callbacks: A Performance-Driven Guide
Nilupul Perera
Nilupul Perera

Posted on

Understanding React's `useRef` and Ref Callbacks: A Performance-Driven Guide

React provides powerful tools for managing DOM references and persisting mutable values with useRef and ref callbacks. These tools help ensure optimal performance, clean integration with third-party libraries, and effective state management. In this article, we’ll explore useRef and ref callbacks, understand their differences, and learn how to use ref callbacks efficiently by wrapping them with useCallback.


What is useRef?

useRef creates a mutable reference object that persists across renders and stores a .current property. This is often used for DOM manipulation or storing values that don’t trigger re-renders.

Key Features of useRef:

  1. Persistent Storage: Maintains its .current property across renders.
  2. No Re-renders: Changes to .current don’t cause a component to re-render.
  3. DOM Manipulation: Commonly used to reference DOM elements.

Example: Using useRef for DOM Manipulation

import React, { useRef } from 'react';

const UseRefExample = () => {
  const inputRef = useRef<HTMLInputElement | null>(null);

  const focusInput = () => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };

  return (
    <div>
      <input ref={inputRef} placeholder="Focus me" />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
};

export default UseRefExample;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • The useRef hook initializes a reference to null.
  • The reference is assigned to the input element when it mounts, making it accessible through inputRef.current.

Ref Callbacks: A Dynamic Alternative

Ref callbacks are functions React calls to dynamically assign or clean up references. Unlike useRef, ref callbacks allow more flexibility in managing dynamic elements.

Key Features of Ref Callbacks:

  1. Dynamic Assignment: Handles DOM element references dynamically on mount/unmount.
  2. Automatic Cleanup: React calls the ref callback with null on unmount, ensuring proper cleanup.
  3. No Persistent State: Unlike useRef, they don’t store values persistently unless explicitly managed.

Example: Ref Callbacks for Dynamic DOM Manipulation

import React, { useCallback } from 'react';

const RefCallbackExample = () => {
  const setRef = useCallback((node: HTMLDivElement | null) => {
    if (node) {
      node.style.backgroundColor = 'lightblue';
      console.log('Node assigned:', node);
    } else {
      console.log('Node unmounted');
    }
  }, []);

  return <div ref={setRef}>Hello, Ref Callback!</div>;
};

export default RefCallbackExample;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • React calls setRef with the DOM element when the component mounts and with null when it unmounts.
  • Using useCallback ensures that the callback remains stable across renders.

Comparing useRef and Ref Callbacks

Feature useRef Ref Callback
Persistence Stores a stable .current property Assigns dynamically on mount/unmount
Reactivity Static and consistent Reactively updated by React
Automatic Cleanup Manual cleanup required React calls with null on unmount

Optimizing Ref Callbacks with useCallback

By default, ref callbacks are re-created on every render. This can lead to unnecessary reassignments and degrade performance. Wrapping ref callbacks with useCallback ensures they remain stable.

Example: Ref Callback with Dynamic Elements

import React, { useCallback, useState } from 'react';

const DynamicRefExample = () => {
  const [showInput, setShowInput] = useState(true);

  const setInputRef = useCallback((node: HTMLInputElement | null) => {
    if (node) {
      console.log('Input mounted:', node);
      node.focus();
    } else {
      console.log('Input unmounted');
    }
  }, []);

  return (
    <div>
      <button onClick={() => setShowInput((prev) => !prev)}>
        Toggle Input
      </button>
      {showInput && <input ref={setInputRef} placeholder="Dynamic Ref" />}
    </div>
  );
};

export default DynamicRefExample;
Enter fullscreen mode Exit fullscreen mode

Use Cases for Ref Callbacks

1. Dynamic DOM Elements

Ref callbacks are ideal when elements are dynamically added or removed.

function DynamicChart({ show }: { show: boolean }) {
  const setChartRef = useCallback((node: HTMLCanvasElement | null) => {
    if (node) {
      console.log('Chart canvas initialized');
    }
  }, []);

  return show ? <canvas ref={setChartRef}></canvas> : null;
}
Enter fullscreen mode Exit fullscreen mode

2. Third-Party Libraries

Ref callbacks simplify integration with libraries like D3.js or Chart.js.

import { Chart } from 'chart.js';

const ChartExample = () => {
  const setCanvasRef = useCallback((canvas: HTMLCanvasElement | null) => {
    if (canvas) {
      const chart = new Chart(canvas, {
        type: 'bar',
        data: { labels: ['A', 'B'], datasets: [{ data: [10, 20] }] },
      });

      return () => chart.destroy();
    }
  }, []);

  return <canvas ref={setCanvasRef}></canvas>;
};
Enter fullscreen mode Exit fullscreen mode

Final Thoughts: Choosing Between useRef and Ref Callbacks

  • Use useRef when:
    • A stable, persistent reference is required (e.g., form inputs).
    • No need for dynamic reassignment.
  • Use ref callbacks when:
    • DOM elements are dynamically added or removed.
    • Cleanup logic is crucial.
    • Third-party libraries require DOM node references.

By understanding the nuances between useRef and ref callbacks, you can choose the right tool for the job and build performant, clean React applications. 🚀

Top comments (0)