React Tutorial 0/120 lessons ~6 min read Lesson 34

    useRef

    useRef in React covers persistent mutable values, DOM references, previous values, third-party instances, and values that should persist without triggering renders.

    Course progress0%
    Focus
    48 guided sections
    Practice signal
    Examples included
    Career prep
    Interview Q&A included

    useRef is a React Hook that lets a component retain a mutable value between renders without causing a re-render when that value changes.

    It is especially useful for:

    • Accessing DOM elements
    • Focusing an input
    • Storing timer IDs
    • Keeping references to external resources
    • Remembering a previous value
    • Storing mutable values that do not belong in rendered UI
    • Integrating React with browser APIs and third-party libraries

    The basic pattern is:

    javascript
    const ref = useRef(initialValue);

    The value is available through:

    javascript
    ref.current

    A useful mental model is:

    text
    useState
    ↓
    Value changes → React renders again
    useRef
    ↓
    Value changes → React does not render again

    Importing useRef

    Import useRef from React:

    javascript
    import { useRef } from "react";

    It can be used alongside other Hooks:

    javascript
    import { useEffect, useRef, useState } from "react";

    Basic Syntax

    The basic syntax is:

    javascript
    const ref = useRef(initialValue);

    For example:

    javascript
    const countRef = useRef(0);

    The returned object looks conceptually like:

    javascript
    {
    current: 0
    }

    You access the stored value through:

    javascript
    countRef.current

    You can update it:

    javascript
    countRef.current = 10;

    Unlike state, changing ref.current does not trigger a re-render.

    useRef Creates a Persistent Reference

    A ref persists across renders.

    For example:

    javascript
    function Example() {
    const valueRef = useRef(0);
    return (
    <button onClick={() => {
    valueRef.current += 1;
    console.log(valueRef.current);
    }}>
    Increase
    </button>
    );
    }

    The value can change:

    text
    0 → 1 → 2 → 3 → 4

    and the ref remains available across renders.

    However, changing the ref itself does not cause React to render the component again.

    useRef vs Normal Variables

    Consider:

    javascript
    function Component() {
    let value = 0;
    function handleClick() {
    value++;
    console.log(value);
    }
    // ...
    }

    A normal local variable is recreated when the component function executes again.

    A ref persists:

    javascript
    function Component() {
    const valueRef = useRef(0);
    function handleClick() {
    valueRef.current++;
    console.log(valueRef.current);
    }
    // ...
    }

    Mental model:

    text
    Normal variable
    ↓
    Component renders
    ↓
    Variable created
    ↓
    Component renders again
    ↓
    Variable created again

    Versus:

    text
    useRef
    ↓
    Initial ref created
    ↓
    Component renders
    ↓
    Component renders again
    ↓
    Same ref object is retained

    useRef vs useState

    This is one of the most important distinctions in React.

    useState

    javascript
    const [count, setCount] = useState(0);

    Updating:

    javascript
    setCount(1);

    can cause React to render again.

    useRef

    javascript
    const countRef = useRef(0);

    Updating:

    javascript
    countRef.current = 1;

    does not cause React to render again.

    Comparison:

    FeatureuseStateuseRef
    Persists between rendersYesYes
    Updating value triggers renderYesNo
    Access valueDirect variable.current
    Intended for rendered UIYesUsually no
    Can store DOM referenceNoYes
    MutableUpdated through settercurrent can be changed

    A practical rule:

    If changing the value should update the UI, use state. If the value needs to persist but changing it should not update the UI, a ref may be appropriate.

    The Most Common useRef Use Case: DOM Access

    Refs are commonly used to access DOM elements.

    Example:

    javascript
    import { useRef } from "react";
    function SearchInput() {
    const inputRef = useRef(null);
    function focusInput() {
    inputRef.current?.focus();
    }
    return (
    <div>
    <input ref={inputRef} />
    <button onClick={focusInput}>
    Focus Input
    </button>
    </div>
    );
    }
    export default SearchInput;

    The connection is:

    text
    <input>
    ↓
    ref={inputRef}
    ↓
    inputRef.current
    ↓
    DOM element

    When the input is mounted, React can assign the DOM element to:

    javascript
    inputRef.current

    Why Does useRef Start With null?

    For DOM refs, the initial value is often:

    javascript
    const inputRef = useRef(null);

    Before the element is mounted:

    text
    inputRef.current = null

    After React attaches the ref:

    text
    inputRef.current = <input DOM node>

    When the element is removed, React can reset the ref.

    Because the element may not exist at every point in the component lifecycle, optional chaining is often useful:

    javascript
    inputRef.current?.focus();

    Accessing the DOM Safely

    This is generally appropriate:

    javascript
    function Input() {
    const inputRef = useRef(null);
    function focusInput() {
    inputRef.current?.focus();
    }
    return (
    <>
    <input ref={inputRef} />
    <button onClick={focusInput}>
    Focus
    </button>
    </>
    );
    }

    Avoid trying to access:

    javascript
    inputRef.current

    during rendering when the DOM element has not been mounted yet.

    The ref is normally available after React has committed the DOM.

    useRef With useEffect

    Refs and effects are frequently used together.

    javascript
    import { useEffect, useRef } from "react";
    function AutoFocusInput() {
    const inputRef = useRef(null);
    useEffect(() => {
    inputRef.current?.focus();
    }, []);
    return <input ref={inputRef} />;
    }

    The sequence is:

    text
    Render
    ↓
    DOM is committed
    ↓
    inputRef.current points to input
    ↓
    Effect runs
    ↓
    focus()

    This is a common React pattern for interacting with the DOM after it exists.

    useRef for Timer IDs

    Refs can store timer IDs.

    javascript
    import { useRef } from "react";
    function Timer() {
    const timerRef = useRef(null);
    function startTimer() {
    timerRef.current = setInterval(() => {
    console.log("Tick");
    }, 1000);
    }
    function stopTimer() {
    if (timerRef.current !== null) {
    clearInterval(timerRef.current);
    timerRef.current = null;
    }
    }
    return (
    <div>
    <button onClick={startTimer}>
    Start
    </button>
    <button onClick={stopTimer}>
    Stop
    </button>
    </div>
    );
    }

    Why use a ref?

    Because the timer ID needs to persist between renders, but changing it does not need to update the UI.

    Cleaning Up Timer Resources

    If an interval should be cleaned up when the component unmounts:

    javascript
    import { useEffect, useRef } from "react";
    function Timer() {
    const timerRef = useRef(null);
    useEffect(() => {
    timerRef.current = setInterval(() => {
    console.log("Tick");
    }, 1000);
    return () => {
    if (timerRef.current !== null) {
    clearInterval(timerRef.current);
    }
    };
    }, []);
    return <p>Timer running</p>;
    }

    The ref stores the resource identifier while the effect manages the lifecycle.

    useRef for Previous Values

    A ref can be used to remember a value from a previous render.

    For example:

    javascript
    import { useEffect, useRef } from "react";
    function User({ userId }) {
    const previousUserId = useRef(null);
    useEffect(() => {
    previousUserId.current = userId;
    }, [userId]);
    return (
    <p>
    Previous user: {previousUserId.current}
    </p>
    );
    }

    The idea is:

    text
    Render 1
    previousUserId = null
    userId = 10
    Effect
    previousUserId.current = 10
    Render 2
    previousUserId = 10
    userId = 20

    A ref provides persistent storage without making the stored value itself part of the rendered state update cycle.

    A Better Previous Value Example

    A reusable Hook can encapsulate the pattern:

    javascript
    import { useEffect, useRef } from "react";
    function usePrevious(value) {
    const ref = useRef();
    useEffect(() => {
    ref.current = value;
    }, [value]);
    return ref.current;
    }

    Usage:

    javascript
    function Counter() {
    const [count, setCount] = useState(0);
    const previousCount = usePrevious(count);
    return (
    <div>
    <p>Current: {count}</p>
    <p>Previous: {previousCount}</p>
    <button
    onClick={() =>
    setCount(previous => previous + 1)
    }
    >
    Increase
    </button>
    </div>
    );
    }

    This introduces the idea of custom Hooks, which will be explored later in the course.

    useRef for Mutable Values

    Refs can store mutable values:

    javascript
    const requestIdRef = useRef(null);
    javascript
    requestIdRef.current = requestId;

    The important characteristic is:

    text
    Value persists
    +
    Value can change
    +
    Changing it does not trigger render

    This makes refs useful for information that belongs to the component's operational logic rather than its visual output.

    useRef Does Not Mean "DOM Only"

    A common beginner misconception is:

    useRef is only for DOM elements.

    That is not true.

    Refs can store:

    text
    DOM nodes
    Timer IDs
    Previous values
    External objects
    Instance-like values
    Request identifiers
    Mutable flags
    Third-party library instances

    DOM access is simply the most visible use case.

    useRef and Third-Party Libraries

    Suppose a third-party chart library needs a DOM container:

    javascript
    function Chart() {
    const chartContainerRef = useRef(null);
    useEffect(() => {
    if (!chartContainerRef.current) {
    return;
    }
    const chart = createChart(
    chartContainerRef.current
    );
    return () => {
    chart.destroy();
    };
    }, []);
    return <div ref={chartContainerRef} />;
    }

    Here:

    text
    useRef
    ↓
    Access DOM container
    useEffect
    ↓
    Create external chart
    cleanup
    ↓
    Destroy chart

    This is a powerful integration pattern.

    useRef and External Objects

    A ref can also retain an external object:

    javascript
    const connectionRef = useRef(null);

    For example:

    javascript
    useEffect(() => {
    const connection = createConnection(roomId);
    connectionRef.current = connection;
    connection.connect();
    return () => {
    connection.disconnect();
    connectionRef.current = null;
    };
    }, [roomId]);

    The ref gives the component a persistent reference to the external resource.

    useRef and Closures

    React function components use JavaScript closures.

    A ref provides a mutable object whose current property can be read by code that has access to that ref.

    For example:

    javascript
    const valueRef = useRef(0);
    function handleClick() {
    valueRef.current += 1;
    }

    The same ref object persists across renders, so event handlers and effects can access its current value.

    This differs from state snapshots, where each render has its own state values.

    State Snapshot vs Ref Value

    Consider:

    javascript
    const [count, setCount] = useState(0);
    const countRef = useRef(0);

    State behaves like a render snapshot:

    text
    Render A
    count = 0

    A state update requests a new render:

    javascript
    setCount(1);

    The current render still has:

    text
    count = 0

    A ref is different:

    javascript
    countRef.current = 1;

    The ref object's current property is mutable.

    Conceptually:

    text
    State
    ↓
    Snapshot associated with render
    Ref
    ↓
    Persistent mutable object

    This difference is important when reasoning about asynchronous callbacks and effects.

    useRef Does Not Trigger Rendering

    Consider:

    javascript
    function Example() {
    const countRef = useRef(0);
    function increment() {
    countRef.current += 1;
    }
    return (
    <div>
    <p>{countRef.current}</p>
    <button onClick={increment}>
    Increment
    </button>
    </div>
    );
    }

    Clicking the button changes:

    javascript
    countRef.current

    but React does not automatically render again.

    Therefore, the displayed <p> may not update.

    If the value needs to appear dynamically in the UI, use state:

    javascript
    const [count, setCount] = useState(0);

    This is a critical rule:

    Do not use a ref for data that must drive rendered UI.

    A Common Mistake

    Incorrect:

    javascript
    const countRef = useRef(0);
    function increment() {
    countRef.current++;
    }

    when the component needs to display:

    javascript
    <p>{countRef.current}</p>

    Use:

    javascript
    const [count, setCount] = useState(0);
    function increment() {
    setCount(previous => previous + 1);
    }

    The decision is based on whether the value affects rendering.

    useRef and DOM Manipulation

    React encourages declarative UI:

    javascript
    return <button disabled={isDisabled}>Save</button>;

    However, some imperative DOM operations are appropriate:

    javascript
    inputRef.current?.focus();
    javascript
    videoRef.current?.play();
    javascript
    elementRef.current?.scrollIntoView();

    Refs provide a controlled escape hatch for these operations.

    Use them when you need direct access to a DOM node or external object, not as a replacement for React's normal rendering model.

    Focus Management Example

    javascript
    import { useRef } from "react";
    function LoginForm() {
    const emailRef = useRef(null);
    function focusEmail() {
    emailRef.current?.focus();
    }
    return (
    <form>
    <input
    ref={emailRef}
    type="email"
    placeholder="Email"
    />
    <button
    type="button"
    onClick={focusEmail}
    >
    Focus Email
    </button>
    </form>
    );
    }
    export default LoginForm;

    Scroll Management Example

    javascript
    import { useRef } from "react";
    function MessageList() {
    const bottomRef = useRef(null);
    function scrollToBottom() {
    bottomRef.current?.scrollIntoView({
    behavior: "smooth"
    });
    }
    return (
    <div>
    <div>
    <p>Message 1</p>
    <p>Message 2</p>
    <p>Message 3</p>
    <div ref={bottomRef} />
    </div>
    <button onClick={scrollToBottom}>
    Scroll to Bottom
    </button>
    </div>
    );
    }
    export default MessageList;

    The ref points to a DOM element that can be used as a scrolling target.

    Video Control Example

    javascript
    import { useRef } from "react";
    function VideoPlayer() {
    const videoRef = useRef(null);
    function playVideo() {
    videoRef.current?.play();
    }
    function pauseVideo() {
    videoRef.current?.pause();
    }
    return (
    <div>
    <video
    ref={videoRef}
    src="/video.mp4"
    controls
    />
    <button onClick={playVideo}>
    Play
    </button>
    <button onClick={pauseVideo}>
    Pause
    </button>
    </div>
    );
    }

    The video element is managed by the browser, while React uses the ref to access it.

    useRef and Conditional Rendering

    A ref may be null when the referenced element is not currently rendered.

    javascript
    function Modal({ isOpen }) {
    const dialogRef = useRef(null);
    // dialogRef.current may be null
    // when the modal is not rendered
    return (
    <>
    {isOpen && (
    <dialog ref={dialogRef}>
    Modal content
    </dialog>
    )}
    </>
    );
    }

    Always consider whether the referenced element exists before accessing it:

    javascript
    dialogRef.current?.focus();

    Callback Refs

    React also supports callback refs.

    Instead of:

    javascript
    const inputRef = useRef(null);
    return <input ref={inputRef} />;

    you can use:

    javascript
    <input
    ref={node => {
    // node is the DOM element
    }}
    />

    Callback refs are useful when ref attachment itself needs custom behavior.

    However, useRef is usually simpler when you only need to retain a DOM reference.

    useRef and Initial Values

    The initial value can be anything:

    javascript
    const numberRef = useRef(0);
    javascript
    const textRef = useRef("");
    javascript
    const objectRef = useRef({});
    javascript
    const elementRef = useRef(null);

    The most common DOM pattern is:

    javascript
    const elementRef = useRef(null);

    because the element does not exist until React mounts it.

    Ref Values Are Mutable

    You can assign:

    javascript
    ref.current = newValue;

    For example:

    javascript
    const statusRef = useRef("idle");
    statusRef.current = "loading";

    But remember:

    text
    Mutation
    ↓
    No automatic render

    If the UI needs to show "loading":

    javascript
    const [status, setStatus] = useState("idle");

    is usually more appropriate.

    useRef and Initialization

    A ref object is created as part of the component's Hook state and remains stable across renders.

    For example:

    javascript
    const inputRef = useRef(null);

    You can think of it as:

    text
    Component instance
    │
    └── inputRef
    │
    └── current

    On future renders, React preserves the ref object.

    Avoid Writing Refs During Rendering

    Although refs are mutable, avoid using them as an ordinary mechanism for changing values during rendering.

    Prefer:

    javascript
    function Component() {
    const valueRef = useRef(0);
    function handleClick() {
    valueRef.current += 1;
    }
    return <button onClick={handleClick}>Click</button>;
    }

    rather than using render-time mutation to coordinate application behavior.

    Rendering should remain predictable and free from unnecessary side effects.

    useRef and Strict Mode

    Development Strict Mode can expose mistakes in component logic and effect setup.

    Refs themselves do not cause renders, but code that initializes external resources through refs still needs correct lifecycle management.

    For example:

    javascript
    useEffect(() => {
    const connection = createConnection();
    connectionRef.current = connection;
    connection.connect();
    return () => {
    connection.disconnect();
    connectionRef.current = null;
    };
    }, []);

    The cleanup makes the external resource lifecycle explicit.

    Common useRef Mistakes

    Mistake 1: Using Ref When UI Must Update

    Incorrect:

    javascript
    const countRef = useRef(0);
    countRef.current++;

    if the updated count needs to be displayed.

    Use state instead:

    javascript
    const [count, setCount] = useState(0);

    Mistake 2: Forgetting .current

    Incorrect:

    javascript
    inputRef.focus();

    Correct:

    javascript
    inputRef.current?.focus();

    Mistake 3: Accessing a Null DOM Ref

    Incorrect:

    javascript
    inputRef.current.focus();

    when the element may not exist.

    Safer:

    javascript
    inputRef.current?.focus();

    Mistake 4: Using Refs as Global State

    Refs belong to a component instance.

    Do not treat:

    javascript
    const ref = useRef();

    as a global application state solution.

    For shared application state, use appropriate React architecture such as lifted state, Context, or another state-management approach when justified.

    Mistake 5: Using Refs to Avoid Correct State Design

    A ref should not be used simply because state updates seem inconvenient.

    Ask:

    Does this value affect the UI?

    If yes, state is often the correct choice.

    Mistake 6: Forgetting Cleanup for Resources

    If a ref stores a timer, connection, subscription, or third-party instance, make sure the resource lifecycle is managed correctly.

    When Should You Use useRef?

    Use useRef when you need:

    1. A DOM reference

    javascript
    const inputRef = useRef(null);

    2. A persistent mutable value

    javascript
    const timerRef = useRef(null);

    3. A previous value

    javascript
    const previousValueRef = useRef();

    4. A third-party instance

    javascript
    const chartRef = useRef(null);

    5. A value that should persist without triggering rendering

    javascript
    const requestIdRef = useRef(null);

    When Should You Use useState Instead?

    Use state when the value:

    • Appears in the rendered UI
    • Determines what the component renders
    • Needs to trigger a render when changed
    • Represents interactive application state

    For example:

    javascript
    const [isOpen, setIsOpen] = useState(false);

    because changing isOpen should change the UI.

    Decision Table

    QuestionUse
    Should changing the value update the UI?useState
    Need a DOM element reference?useRef
    Need a timer ID between renders?useRef
    Need previous value storage?useRef
    Need a selected tab displayed in UI?useState
    Need to retain a third-party instance?useRef
    Need fetched data displayed in UI?useState
    Need an external connection reference?useRef

    useRef + useState Example

    Sometimes both Hooks are appropriate.

    javascript
    import { useRef, useState } from "react";
    function Stopwatch() {
    const [seconds, setSeconds] = useState(0);
    const intervalRef = useRef(null);
    function start() {
    if (intervalRef.current !== null) {
    return;
    }
    intervalRef.current = setInterval(() => {
    setSeconds(previous => previous + 1);
    }, 1000);
    }
    function stop() {
    if (intervalRef.current !== null) {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
    }
    }
    return (
    <div>
    <p>{seconds}s</p>
    <button onClick={start}>
    Start
    </button>
    <button onClick={stop}>
    Stop
    </button>
    </div>
    );
    }

    Here:

    text
    seconds
    ↓
    useState
    ↓
    Displayed in UI
    interval ID
    ↓
    useRef
    ↓
    Operational value

    This is a very useful real-world pattern.

    useRef + useEffect Example

    Another common combination is:

    javascript
    import { useEffect, useRef } from "react";
    function Example() {
    const inputRef = useRef(null);
    useEffect(() => {
    inputRef.current?.focus();
    }, []);
    return <input ref={inputRef} />;
    }

    The responsibilities are clear:

    text
    useRef
    ↓
    Stores DOM reference
    useEffect
    ↓
    Runs synchronization after mount

    useRef + Previous State Example

    javascript
    import { useEffect, useRef, useState } from "react";
    function Counter() {
    const [count, setCount] = useState(0);
    const previousCountRef = useRef();
    useEffect(() => {
    previousCountRef.current = count;
    }, [count]);
    return (
    <div>
    <p>Current: {count}</p>
    <p>Previous: {previousCountRef.current}</p>
    <button
    onClick={() =>
    setCount(previous => previous + 1)
    }
    >
    Increase
    </button>
    </div>
    );
    }

    The ref stores information between renders without becoming another piece of state.

    Practical Example: Prevent Duplicate Actions

    A ref can sometimes track an operational flag:

    javascript
    function SubmitButton() {
    const submittingRef = useRef(false);
    async function handleSubmit() {
    if (submittingRef.current) {
    return;
    }
    submittingRef.current = true;
    try {
    await submitForm();
    } finally {
    submittingRef.current = false;
    }
    }
    return (
    <button onClick={handleSubmit}>
    Submit
    </button>
    );
    }

    However, if the UI needs to display a loading state, use state as well:

    javascript
    const [isSubmitting, setIsSubmitting] = useState(false);

    A ref is appropriate for an internal operational value; state is appropriate for UI state.

    Practical Example: Tracking Render Count

    A ref can be used for diagnostics:

    javascript
    function DebugComponent() {
    const renderCount = useRef(0);
    renderCount.current += 1;
    return (
    <p>
    Render count: {renderCount.current}
    </p>
    );
    }

    This is useful for learning and debugging, but be careful about mutating refs during render in production application logic. Prefer refs for stable operational data and debugging scenarios rather than using them to drive application behavior.

    useRef and Performance

    Because changing a ref does not trigger rendering, refs can avoid unnecessary renders for values that do not belong in the UI.

    However, this does not mean:

    useRef is a faster version of useState.

    They have different purposes.

    Do not replace state with refs merely to reduce renders.

    Correct design comes first:

    text
    UI data
    ↓
    useState
    Non-UI persistent value
    ↓
    useRef

    useRef and Component Architecture

    Refs are usually local to the component that owns them.

    If a parent needs to interact with a child's DOM or imperative behavior, React provides patterns such as passing refs through component boundaries and, in appropriate cases, exposing imperative methods.

    These advanced patterns should be used sparingly because declarative props are generally easier to reason about.

    Best Practices

    1. Use State for Rendered Data

    If the value affects JSX:

    javascript
    useState(...)

    is usually the correct choice.

    2. Use Refs for Persistent Non-UI Values

    Examples:

    text
    Timer IDs
    DOM nodes
    Previous values
    External instances
    Request identifiers

    3. Use .current

    Remember:

    javascript
    ref.current

    not:

    javascript
    ref

    when accessing the stored value.

    4. Check for Null DOM Refs

    Prefer:

    javascript
    inputRef.current?.focus();

    when the element may not exist.

    5. Pair Resource Refs With Cleanup

    If a ref stores a resource:

    javascript
    connectionRef.current

    make sure the corresponding resource is correctly cleaned up.

    6. Do Not Treat Refs as Global State

    A ref belongs to a particular component instance.

    7. Do Not Use Refs to Bypass React's Rendering Model

    Refs are an escape hatch, not a replacement for declarative state and props.

    Interview questions & answers

    Interview preparation

    10 questions grouped by difficulty — expand any card for a model answer and follow-up probe.

    Beginner

    4
    1. 1What is useRef?
      Beginner

      Model answer

      useRef is a React Hook that provides a persistent mutable reference whose .current value can be changed without causing a re-render.
    2. 2What is the most common use of useRef?
      Beginner

      Model answer

      Accessing DOM elements: const inputRef = useRef(null); → <input ref={inputRef} />
    3. 3Why is useRef initialized with null for DOM elements?
      Beginner

      Model answer

      Because the DOM element does not exist before React mounts it.

    4. 4Why are refs useful with timers?
      Beginner

      Model answer

      A timer ID needs to persist between renders so that it can later be cancelled, but changing the timer ID usually does not need to update the UI.

    Intermediate

    3
    1. 5What does useRef return?
      Intermediate

      Model answer

      It returns an object with a current property: const ref = useRef(initialValue); Conceptually: { → current: initialValue → }
    2. 6What is the difference between useRef and useState?
      Intermediate

      Model answer

      Both persist values between renders. The key difference is that updating state can trigger a render, while updating ref.current does not.
    3. 7Can a ref be used to update UI?
      Intermediate

      Model answer

      Not by itself. Changing: ref.current does not trigger a render. Use state if the UI needs to update.

    Advanced

    3
    1. 8Does changing ref.current trigger a render?
      Advanced

      Model answer

      No. ref.current = newValue; does not itself trigger a React render.
    2. 9Can useRef store values other than DOM nodes?
      Advanced

      Model answer

      Yes. It can store:
      • Timer IDs
      • Previous values
      • External instances
      • Mutable operational values
      • Request identifiers
      • DOM nodes
    3. 10Can useRef replace state?
      Advanced

      Model answer

      No.

      They solve different problems.

      Use state for data that drives rendering and refs for persistent values that do not need to trigger rendering.

    Practice Exercises

    Exercise 1: Auto-Focus

    Create a component with:

    • An input
    • A button
    • A ref attached to the input
    • A button that focuses the input

    Exercise 2: Scroll to Bottom

    Create a message list where:

    • A ref points to the bottom element.
    • Clicking a button scrolls to the bottom.

    Exercise 3: Previous Value

    Create a component that:

    • Uses state for a counter.
    • Uses a ref to remember the previous counter value.
    • Displays current and previous values.

    Exercise 4: Timer

    Create a timer that:

    • Uses state for elapsed seconds.
    • Uses a ref for the interval ID.
    • Has Start and Stop buttons.
    • Cleans up the interval.

    Exercise 5: Video Player

    Create a custom video controller with:

    • Play button
    • Pause button
    • Video element
    • Ref attached to the video

    Exercise 6: Window Resize

    Create a component that:

    • Uses state for the displayed window width.
    • Uses an effect for the resize listener.
    • Uses cleanup to remove the listener.

    Then explain why the width belongs in state rather than a ref.

    Mini Project: Stopwatch

    Build a reusable stopwatch.

    Requirements:

    text
    Display:
    00:00

    Buttons:

    text
    Start
    Stop
    Reset

    Use:

    text
    useState → elapsed time
    useRef → interval ID
    useEffect → cleanup

    A possible implementation:

    javascript
    import { useEffect, useRef, useState } from "react";
    function Stopwatch() {
    const [seconds, setSeconds] = useState(0);
    const intervalRef = useRef(null);
    function start() {
    if (intervalRef.current !== null) {
    return;
    }
    intervalRef.current = setInterval(() => {
    setSeconds(previous => previous + 1);
    }, 1000);
    }
    function stop() {
    if (intervalRef.current !== null) {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
    }
    }
    function reset() {
    stop();
    setSeconds(0);
    }
    useEffect(() => {
    return () => {
    if (intervalRef.current !== null) {
    clearInterval(intervalRef.current);
    }
    };
    }, []);
    return (
    <div>
    <h2>{seconds}s</h2>
    <button onClick={start}>
    Start
    </button>
    <button onClick={stop}>
    Stop
    </button>
    <button onClick={reset}>
    Reset
    </button>
    </div>
    );
    }
    export default Stopwatch;

    The architecture is:

    text
    Stopwatch
    │
    ┌─────────┴─────────┐
    │ │
    elapsed time interval ID
    │ │
    useState useRef
    │ │
    drives UI operational data
    │ │
    └─────────┬─────────┘
    │
    useEffect
    │
    cleanup

    Quick Cheat Sheet

    Import

    javascript
    import { useRef } from "react";

    Create ref

    javascript
    const ref = useRef(null);

    Read value

    javascript
    ref.current

    Update value

    javascript
    ref.current = newValue;

    DOM reference

    javascript
    const inputRef = useRef(null);
    <input ref={inputRef} />

    Focus

    javascript
    inputRef.current?.focus();

    Timer ID

    javascript
    const timerRef = useRef(null);

    Previous value

    javascript
    const previousRef = useRef();
    useEffect(() => {
    previousRef.current = value;
    }, [value]);

    State vs Ref

    text
    useState → changing UI data
    useRef → persistent non-UI data

    Key Takeaways

    Ready to mark this lesson complete?Track your journey across the entire course.