BRUCE_FEBRUCE_FE

EN/CH Mode

BRUCE_FE React Interview Notes - useRef Usage and Implementation

Deep dive into React useRef's various uses, internal implementation, and usage techniques. Master accessing DOM elements with useRef, storing variables without triggering re-renders, understand its differences from useState, and common useRef interview questions and answers.

影片縮圖

Lazy to read articles? Then watch videos!

Introduction to useRef and Its Utility

useRef is one of React's Hooks, used to create a mutable reference that persists throughout a component's lifecycle. Its key feature is: when the ref's content changes, it doesn't trigger a component re-render.

useRef vs useState in Render Cycles

State Changes and Rendering Relationship:

useRef

Render 1: ref.current = 0

Modify value: ref.current = 1

→ No re-render triggered

Still render 1: ref.current = 1

useState

Render 1: [value=0, setValue]

Modify value: setValue(1)

→ Triggers re-render

Render 2: [value=1, setValue]

Reference Consistency Comparison:

useRef Maintains Same Reference

useRef returns the same reference object throughout the component's lifecycle. Even when the component re-renders, the ref object's identity remains unchanged, only the current property's value may change.

// First render
const ref = useRef(0);  // ref = { current: 0 }

// After multiple renders, ref is still the same object
console.log(ref === previousRenderRef);  // true

useState Creates New Value Each Render

useState creates a new snapshot of the current state in each render. Although the state value might be the same, the state variable in each render is an independent constant.

// Render 1
const [count, setCount] = useState(0);
// count in this render is a snapshot of 0

// Render 2 (after setCount(1))
const [count, setCount] = useState(0);
// count in this render is a new snapshot of 1

Real Example: Counter Without Re-rendering

function SilentCounter() {
  const [, forceUpdate] = useState({});
  const countRef = useRef(0);
  
  return (
    <>
      <p>Count: {countRef.current}</p>
      <button onClick={() => {
        // Increment count without re-rendering
        countRef.current += 1;
        console.log("Count increased to:", countRef.current);
      }}>
        Increment Count (No UI Update)
      </button>
      <button onClick={() => forceUpdate({})}>
        Manual UI Update
      </button>
    </>
  );
}

Three Main Uses of useRef

1. Accessing DOM Elements

This is the most common use of useRef, directly accessing DOM nodes for imperative operations (e.g., focus, video playback, size calculations).

  • 1. Need to manipulate DOM elements
  • 2. Store data that doesn't affect UI (timer IDs, etc.)
  • 3. Need to remember values without triggering re-renders
  1. 1. Create a plain JavaScript object with a current property
  2. 2. Ensure this object maintains a stable reference between renders
  3. 3. Provide no active notification mechanism when ref content changes
function AutoPlayVideo() {
  const videoRef = useRef(null);
  
  useEffect(() => {
    // Auto-play when component mounts
    videoRef.current.play();
    
    return () => {
      // Pause when component unmounts
      videoRef.current.pause();
    };
  }, []);
  
  return <video ref={videoRef} src="https://example.com/video.mp4" />;
}

2. Store Mutable Values (Without Triggering Re-renders)

useRef can store values of any type and updating them won't trigger re-renders, making it ideal for storing data that doesn't need to participate in render calculations.

function IntervalCounter() {
  const [count, setCount] = useState(0);
  // Store interval ID for cleanup
  const intervalRef = useRef(null);
  // Record count value from last render
  const previousCountRef = useRef(0);
  
  useEffect(() => {
    // Save previous count (runs after each render)
    previousCountRef.current = count;
    
    // Set up interval
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    
    return () => {
      // Clean up interval
      clearInterval(intervalRef.current);
    };
  }, []);
  
  return (
    <div>
      Now: {count}, Previous: {previousCountRef.current}
    </div>
  );
}

3. Cache Computation Results (Similar to Memoization)

When you need to store results of expensive calculations but don't want to recalculate on every re-render, useRef can serve as a lightweight alternative.

function SearchResults({ query }) {
  const [results, setResults] = useState([]);
  // Use ref to cache previous search results
  const prevQueryRef = useRef('');
  const cachedResultsRef = useRef({});
  
  useEffect(() => {
    // If query hasn't changed or we have cached results, use them
    if (query === prevQueryRef.current && cachedResultsRef.current[query]) {
      setResults(cachedResultsRef.current[query]);
      return;
    }
    
    // Otherwise perform search and store results
    fetchSearchResults(query).then(data => {
      setResults(data);
      prevQueryRef.current = query;
      cachedResultsRef.current[query] = data;
    });
  }, [query]);
  
  return (
    <ul>
      {results.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

useRef vs useState: When to Use Which?

Key Differences

FeatureuseStateuseRef
Triggers Re-render on UpdateYes ✅No ❌
Value Access MethodDirect Variable AccessThrough .current Property
Update MethodUsing Setter FunctionDirectly Modify .current
Maintains Value Across Re-rendersYes ✅Yes ✅
Applicable ScenariosData Needed for UI DisplayValues Not Involved in Rendering or DOM References

Selection Guide

When to use useState:

  • 1. When data changes need to be immediately reflected in the UI
  • 2. When you need to trigger component re-renders to update the view
  • 3. When the value affects render results or other state/effect dependencies

When to use useRef:

  • 1. Store values that don't affect render output (timer IDs, previous props, etc.)
  • 2. Need direct access to DOM elements
  • 3. Need to store values that persist throughout component lifecycle without triggering updates
  • 4. Optimize performance by avoiding unnecessary re-renders
// Consider this counter example, showing two different implementations
function CounterExample() {
  // Updates will trigger re-render
  const [stateCount, setStateCount] = useState(0);
  // Updates will not trigger re-render
  const refCount = useRef(0);
  
  return (
    <>
      <div>
        <h3>useState Counter: {stateCount}</h3>
        <button onClick={() => setStateCount(c => c + 1)}>
          Increment State Count
        </button>
      </div>
      
      <div>
        <h3>useRef Counter: {refCount.current}</h3>
        <button 
          onClick={() => {
            refCount.current += 1;
            // Note: Manual force re-render needed to see updates
            console.log('useRef value updated, but UI won't update automatically');
          }}
        >
          Increment Ref Count (No UI Update)
        </button>
      </div>
    </>
  );
}

Internal Implementation of useRef

Understanding useRef's implementation helps us better grasp its behavioral characteristics. Inside React, useRef's implementation is quite simple:

// Simplified implementation of useRef inside React
function useRef(initialValue) {
  const hookIndex = currentComponent.currentHookIndex++;

  if (!currentComponent.hooks[hookIndex]) {
    currentComponent.hooks[hookIndex] = { current: initialValue };
  }

  return currentComponent.hooks[hookIndex];
}

In essence, the core of useRef's implementation lies in:

  1. 1. Creating a plain JavaScript object with a current property
  2. 2. Ensuring this object maintains a stable reference between renders
  3. 3. Not providing any active notification mechanism when ref content changes

In React's Fiber architecture, useRef's state is also stored in the Fiber node's memoizedState as part of the Hook linked list:

// Hook structure in React Fiber
{
  memoizedState: {
    /* useState Hook node */
    // ...
    next: {
      // useRef Hook node
      memoizedState: { current: initialValue },
      next: {
        /* Next Hook */
        // ...
      }
    }
  }
}

Advanced Techniques and Patterns

Using useRef to Store Previous Props or State

In some scenarios, when you need to compare current values with previous ones, useRef is the ideal tool:

function usePrevious(value) {
  const ref = useRef();
  
  useEffect(() => {
    // Update ref value after each render
    ref.current = value;
  }, [value]);
  
  // Return the previous value
  return ref.current;
}

function ProfileUpdater({ userId }) {
  const prevUserId = usePrevious(userId);
  
  useEffect(() => {
    // Only execute when user changes
    if (prevUserId !== userId) {
      console.log(`User switched from ${prevUserId} to ${userId}`);
      fetchUserData(userId);
    }
  }, [userId, prevUserId]);
  
  // Rest of the component
}

Conditional DOM References

When elements might not exist or are conditionally rendered, refs need to be handled carefully:

function ConditionalRefExample({ showInput }) {
  const inputRef = useRef(null);
  
  // Use with useLayoutEffect to ensure safe DOM operations
  useLayoutEffect(() => {
    // Safely check if element exists
    if (showInput && inputRef.current) {
      inputRef.current.focus();
    }
  }, [showInput]);
  
  return (
    <div>
      {showInput && (
        <input ref={inputRef} type="text" placeholder="I will auto-focus" />
      )}
      {/* ref.current is null when element doesn't exist */}
    </div>
  );
}

🔥 Common Interview Questions

(1) What is useRef? What are its main uses?

Answer: useRef is a React Hook that creates a "memory box" where its contents persist even when the component re-renders.

useRef Box

.current

Modifications don't trigger re-renders

Main uses:

  • 1. Access DOM elements (e.g., auto-focus input fields)
  • 2. Store data that doesn't need to trigger re-renders
  • 3. Remember previous state values
  • 4. Store values like timer IDs that need to persist across render cycles

(2) What's the difference between useRef and useState?

useState

Modify value → Triggers re-render

Update using setter function

Direct access: value

useRef

Modify value → No re-render

Directly modify .current

Access: ref.current

(3) Why doesn't modifying useRef trigger re-renders?

Answer: This is related to React's design:

React Rendering Mechanism Diagram

useState

Notify React to update

Trigger re-render

useRef

Silently update .current

React unaware of change

In simple terms:

  • 1. useState has a 'notification mechanism' to tell React UI updates are needed
  • 2. useRef is just a container, modifications don't notify React
  • 3. React only cares about update requests through official channels (like setState)