React useRef Hook

  • useRef is a built-in React hook that allows you to create a "persistence layer" for values that does not trigger a component re-render when the value changes.
  • Think of it as a "box" or a container that stays the same for the entire lifecycle of your component, even when the component updates.
Developer Tip: Use useRef when you need to store data that is necessary for your logic but shouldn't affect what the user sees on the screen (the UI). If changing the value should update the view, use useState instead.

Purpose

  • DOM Interaction: Directly reaching into the browser's DOM to focus an input, measure an element's size, or integrate with third-party libraries like D3.js or Google Maps.
  • Mutable Variables: Storing variables that change over time but shouldn't cause the component to "flicker" or re-process the JSX.
  • Persistence: Keeping track of values (like interval IDs or previous props) that must survive across multiple render cycles.
Watch Out: Modifying myRef.current is a side effect. You generally shouldn't read or write to the ref during the "render phase" (inside the main body of your function component). Instead, do it inside useEffect or event handlers.

Syntax

  • To use the hook, import it from 'react' and initialize it. The value you pass to useRef() becomes the initial value of the current property.
import React, { useRef } from 'react';

// myRef becomes { current: initialValue }
const myRef = useRef(initialValue);

Accessing DOM Elements

  • The most common use case for useRef is interacting with a specific HTML element once it has been rendered to the screen.
import React, { useRef, useEffect } from 'react';

const DomManipulationExample = () => {
  // 1. Create the ref with an initial value of null
  const inputRef = useRef(null);

  useEffect(() => {
    // 3. React assigns the DOM node to inputRef.current once it mounts
    // We can now call native DOM methods like .focus()
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);

  return (
    <div>
      <h2>Example: Accessing DOM Elements</h2>
      {/* 2. Attach the ref to the element using the 'ref' attribute */}
      <input ref={inputRef} type="text" placeholder="I will be focused on load" />
    </div>
  );
};
Common Mistake: Forgetting that useRef returns an object. Beginners often try to access the value directly (e.g., inputRef.focus()) instead of using inputRef.current.focus().

Persisting Values

  • When you update a useState variable, React re-runs your entire function to update the UI. If you update a useRef value, React doesn't "notice," allowing you to track data silently.
import React, { useRef, useState } from 'react';

const PersistingValueExample = () => {
  const [dummyState, setDummyState] = useState(0);
  const renderCount = useRef(0);

  // This function changes the ref, but the UI won't show the new number 
  // until something else triggers a re-render.
  const incrementRenderCount = () => {
    renderCount.current += 1;
    console.log('Internal Count (No Render):', renderCount.current);
  };

  return (
    <div>
      <h2>Example: Persisting Values</h2>
      <p>This value only updates visually when the component re-renders: {renderCount.current}</p>
      <button onClick={incrementRenderCount}>Increment Ref (Silent)</button>
      <button onClick={() => setDummyState(dummyState + 1)}>Force Re-render</button>
    </div>
  );
};
Best Practice: Use useRef for storing Timer IDs (from setTimeout or setInterval). Since these don't need to be displayed in the UI, keeping them in a ref prevents unnecessary re-renders when the ID is set.

Storing Previous State

  • Because useEffect runs after the render is committed to the screen, we can use a ref to "capture" the state of a variable before it changes in the next cycle.
import React, { useRef, useState, useEffect } from 'react';

const PreviousStateExample = () => {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();

  useEffect(() => {
    // This runs AFTER the render, so it stores the "current" count
    // which becomes the "previous" count during the NEXT render.
    prevCountRef.current = count;
  }, [count]);

  const prevCount = prevCountRef.current;

  return (
    <div>
      <h2>Example: Storing Previous State</h2>
      <p>Current Count: {count}</p>
      <p>Previous Count: {prevCount}</p>
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
    </div>
  );
};

 

Summary

useRef is a versatile tool in the React developer's kit. While useState is the go-to for data that drives the user interface, useRef is your best friend for manual DOM control, performance optimizations, and keeping track of variables that should stay "behind the scenes."

  • Ref Object: Always accessed via .current.
  • No Re-renders: Updating a ref does not trigger the component to refresh.
  • Persistence: The value remains intact even if the component re-renders due to other state changes.