React

Chapter 09: useRef & DOM Interaction — Accessing real DOM nodes, storing mutable values without re-renders, previous state tracking, and video/audio control.

1. What is useRef?

useRef is a built-in React hook that returns a mutable object: { current: initialValue }. It has two distinct superpowers:

  • Persists across renders: Unlike normal local variables, its value is remembered between render cycles.
  • Does NOT trigger a re-render: Mutating ref.current = newValue modifies data silently without asking React to recalculate the UI.

💡 Real-World Analogy: Secret Pocket Notebook vs Wall Whiteboard

Think of storing data inside a component:

  • State (Wall Whiteboard): Whenever you change something on the whiteboard, the whole room stops and updates their notes (causes a re-render).
  • useRef (Secret Pocket Notebook): You take out your notebook, jot down a timer ID or secret coordinate, and put it back in your pocket. No one in the room notices, and nothing re-renders!

2. State vs Ref vs Regular Variable

01

useState

UI Data

  • Persists across renders.
  • Triggers re-render on change.
  • Use for values displayed on screen.
02

useRef

Silent Memory & DOM

  • Persists across renders.
  • Never triggers re-render.
  • Use for DOM nodes, timer IDs, previous values.
03

let / const

Transient Data

  • Resets every render.
  • No re-render trigger.
  • Use only for temporary derived math.

3. Use Case 1: Direct DOM Element Access

Attach a ref to any JSX element via the ref attribute. After React mounts the real DOM node, it assigns the underlying HTML node to ref.current:

function AutoFocusSearch() {
    const inputRef = React.useRef(null); // 1. Create Ref

    function handleFocusClick() {
        // 2. Direct DOM method execution:
        inputRef.current.focus();
        inputRef.current.style.borderColor = "#6C4CE8";
    }

    React.useEffect(() => {
        // Auto-focus input on initial page load:
        if (inputRef.current) {
            inputRef.current.focus();
        }
    }, []);

    return (
        <div className="search-box">
            <input ref={inputRef} placeholder="Search documentation..." />
            <button onClick={handleFocusClick}>Focus Input</button>
        </div>
    );
}
Interactive DOM Preview:

4. Use Case 2: Storing Mutable Values (Timers & Previous State)

Because modifying ref.current does not cause a re-render, it is the ideal place to store interval IDs, click debouncers, or track previous state snapshots:

function Stopwatch() {
    const [seconds, setSeconds] = React.useState(0);
    const timerRef = React.useRef(null); // Stores timer ID silently

    function startTimer() {
        if (timerRef.current !== null) return; // Already running
        timerRef.current = setInterval(() => {
            setSeconds(prev => prev + 1);
        }, 1000);
    }

    function stopTimer() {
        clearInterval(timerRef.current);
        timerRef.current = null; // Clear reference
    }

    return (
        <div>
            <h3>Time: {seconds}s</h3>
            <button onClick={startTimer}>Start</button>
            <button onClick={stopTimer}>Stop</button>
        </div>
    );
}
Silent Ref Memory Output:
[Stopwatch] timerRef.current = 1042 (Stored silently in ref without re-render) [Timer Tick] setSeconds(prev => prev + 1) triggered UI re-render -> Time: 5s [Stopwatch] clearInterval(1042) executed cleanly!

5. Hands-On Practice Challenges

Master direct DOM operations and persistent ref containers with these interactive coding exercises. Click "Solve Challenge" to open the editor!

🎯 Hands-on Coding Challenge Ref Internals

Challenge 1: Replicate useRef Container Object

Write a factory function createRefObject(initialValue) that creates and returns a persistent mutable ref container object { current: initialValue }.

📋 Requirements & What to Write:
  • Return an object with a single current property.
  • Initialize current with initialValue (default null).
  • Ensure mutating current updates the object reference in place.
Expected Result: Ref initial value: "initial_text" | Ref updated value: "updated_text"
💡
Editor Instructions: Complete createRefObject to construct the mutable reference box.
refFactory.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge State History Tracker

Challenge 2: Previous State Tracker Simulation

Implement a closure trackPreviousState() that stores the latest value and returns both the current value and the previous value on each update.

📋 Requirements & What to Write:
  • Track currentVal and prevVal in closure memory.
  • On update(newVal), slide currentVal to prevVal.
  • Return { current, previous }.
Expected Result: Step 1: { current: "First", previous: null } | Step 2: { current: "Second", previous: "First" }
💡
Editor Instructions: Complete trackPreviousState and click "▶ Run Code".
prevTracker.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge Render Diagnostics

Challenge 3: Silent Render Counter with Ref

Write a function createRenderCounter() that uses a ref object to increment and return the render count on every call without triggering any state updates.

📋 Requirements & What to Write:
  • Store counter in a { current: 0 } ref container.
  • Increment ref.current on each invocation.
  • Return string formatted with current count.
Expected Result: Component has rendered 1 times. | Component has rendered 2 times.
💡
Editor Instructions: Complete createRenderCounter and run the code.
renderCounter.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...