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

    Side Effects Management

    Side effects management with Hooks covers identifying external systems, cleanup, data fetching patterns, subscriptions, and keeping Effects focused.

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

    Side effects are an essential part of real-world React applications.

    A React component primarily describes what the UI should look like for the current state and props. But applications also need to interact with systems outside React, such as:

    • APIs
    • browser APIs
    • timers
    • event listeners
    • WebSockets
    • subscriptions
    • local storage
    • third-party libraries
    • analytics systems
    • DOM APIs

    These interactions are called side effects.

    React provides tools such as useEffect, event handlers, and other Hooks to manage them in a predictable way.

    The goal is not to eliminate side effects.

    The goal is to place side effects in the correct part of the application and keep them synchronized with the values they depend on.

    What Is a Side Effect?

    A side effect is work that reaches outside the component's rendering calculation or changes something external to React.

    For example:

    javascript
    document.title = "Dashboard";

    This changes the browser environment.

    Another example:

    javascript
    localStorage.setItem("theme", "dark");

    This changes browser storage.

    An API request:

    javascript
    fetch("/api/products");

    interacts with an external system.

    A timer:

    javascript
    setTimeout(() => {
    // ...
    }, 1000);

    also interacts with something outside React's rendering model.

    Pure Rendering vs Side Effects

    React rendering should be treated as a calculation.

    Conceptually:

    text
    Props + State
    ↓
    Render
    ↓
    JSX

    The same inputs should produce the same UI description.

    Side effects are different:

    text
    React
    ↓
    External System

    Examples:

    text
    Component
    ↓
    Browser API
    text
    Component
    ↓
    Network
    text
    Component
    ↓
    WebSocket

    Keeping these responsibilities separate makes components easier to understand.

    Common Side Effects

    Real applications commonly perform side effects such as:

    Network Requests

    javascript
    fetch("/api/products");

    Browser APIs

    javascript
    window.addEventListener("resize", handleResize);

    Document APIs

    javascript
    document.title = "Products";

    Storage

    javascript
    localStorage.setItem("theme", theme);

    Timers

    javascript
    setTimeout(callback, 1000);

    Subscriptions

    javascript
    socket.subscribe(handleMessage);

    Third-Party Libraries

    javascript
    const chart = new Chart(element, options);

    Each needs an appropriate lifecycle and cleanup strategy.

    The Main Tool: useEffect

    useEffect allows a component to synchronize with an external system.

    Basic syntax:

    javascript
    useEffect(() => {
    // setup or synchronization
    return () => {
    // cleanup
    };
    }, [dependencies]);

    The important parts are:

    • setup logic
    • optional cleanup logic
    • dependency list

    Effects Are About Synchronization

    A useful way to think about an Effect is:

    When these reactive values have these current values, make an external system match them.

    For example:

    javascript
    useEffect(() => {
    document.title = title;
    }, [title]);

    The Effect keeps the browser document title synchronized with the React value title.

    This is more useful than thinking of Effects simply as "code that runs after rendering."

    Effect Lifecycle

    An Effect has a setup and optional cleanup.

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

    Conceptually:

    text
    Render
    ↓
    Effect setup
    ↓
    External system synchronized
    ↓
    Dependencies change
    ↓
    Cleanup previous Effect
    ↓
    Setup next Effect
    ↓
    Component unmounts
    ↓
    Cleanup

    Cleanup is important whenever the Effect creates something that needs to be stopped, removed, or disconnected.

    Dependency Arrays

    Dependencies tell React which reactive values the Effect uses.

    Example:

    javascript
    useEffect(() => {
    document.title = title;
    }, [title]);

    The Effect depends on title.

    When title changes, React can synchronize the Effect again.

    Another example:

    javascript
    useEffect(() => {
    connectToRoom(roomId);
    }, [roomId]);

    The connection depends on roomId.

    Empty Dependency Array

    An Effect with an empty dependency array:

    javascript
    useEffect(() => {
    // setup
    }, []);

    does not depend on changing reactive values from the component.

    For a mounted component, this Effect's setup normally runs after the initial commit, with cleanup when the component is removed. In development Strict Mode, React may intentionally perform an extra setup/cleanup cycle to help reveal unsafe Effect logic.

    Do not interpret [] as "run exactly once under every circumstance."

    The important idea is that the Effect has no changing dependencies.

    No Dependency Array

    If you write:

    javascript
    useEffect(() => {
    // ...
    });

    the Effect runs after every committed render.

    This can be useful in specific synchronization scenarios, but it is often unnecessary.

    If an Effect only needs to synchronize when particular values change, provide those dependencies.

    Cleanup Functions

    Cleanup stops or reverses the external work created by an Effect.

    Example:

    javascript
    useEffect(() => {
    function handleResize() {
    console.log(window.innerWidth);
    }
    window.addEventListener(
    "resize",
    handleResize
    );
    return () => {
    window.removeEventListener(
    "resize",
    handleResize
    );
    };
    }, []);

    The cleanup removes the event listener.

    Without cleanup, repeated mounting or dependency changes can create unwanted listeners.

    What Requires Cleanup?

    Cleanup is commonly required for:

    • event listeners
    • subscriptions
    • timers
    • WebSocket connections
    • observers
    • external library instances
    • pending operations that can be cancelled

    Not every Effect needs cleanup.

    For example:

    javascript
    useEffect(() => {
    document.title = title;
    }, [title]);

    does not require cleanup because there is nothing to unsubscribe or disconnect.

    Event Handlers vs Effects

    One of the most important distinctions in React is deciding whether code belongs in an event handler or an Effect.

    Consider:

    javascript
    function handleSubmit() {
    sendAnalyticsEvent("form_submitted");
    }

    This is caused by a user interaction.

    An event handler is usually the right place.

    An Effect is appropriate when synchronization should happen because component state or props changed.

    For example:

    javascript
    useEffect(() => {
    document.title = `${count} items`;
    }, [count]);

    Simple rule

    text
    User interaction caused it
    ↓
    Event handler
    State/props changed and an external system
    must stay synchronized
    ↓
    Effect

    Do Not Use Effects for Derived Data

    Suppose:

    javascript
    const [firstName, setFirstName] = useState("");
    const [lastName, setLastName] = useState("");

    You might be tempted to write:

    javascript
    const [fullName, setFullName] = useState("");
    useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
    }, [firstName, lastName]);

    This is usually unnecessary.

    fullName can be calculated during rendering:

    javascript
    const fullName = `${firstName} ${lastName}`;

    The value is derived from existing state.

    No external synchronization is needed.

    Avoid Effect Chains

    Avoid creating a chain of Effects that update state in sequence:

    text
    state A
    ↓
    Effect
    ↓
    state B
    ↓
    Effect
    ↓
    state C

    This often makes the application harder to reason about.

    Prefer deriving values when possible.

    For example:

    javascript
    const filteredProducts = products.filter(...);
    const total = filteredProducts.reduce(...);

    instead of storing both derived values and synchronizing them through Effects.

    API Requests

    Fetching data is a common Effect use case when the component needs to synchronize with an external data source.

    A simplified example:

    javascript
    import { useEffect, useState } from "react";
    function Products() {
    const [products, setProducts] = useState([]);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
    let ignore = false;
    async function loadProducts() {
    setLoading(true);
    const response = await fetch("/api/products");
    const data = await response.json();
    if (!ignore) {
    setProducts(data);
    setLoading(false);
    }
    }
    loadProducts();
    return () => {
    ignore = true;
    };
    }, []);
    if (loading) {
    return <p>Loading...</p>;
    }
    return (
    <ul>
    {products.map((product) => (
    <li key={product.id}>
    {product.name}
    </li>
    ))}
    </ul>
    );
    }

    The ignore pattern prevents an outdated request from updating state after the Effect has been cleaned up.

    For production applications, dedicated data-fetching libraries can provide more complete caching, synchronization, retry, and request-management capabilities.

    Handling Request Errors

    Effects that perform asynchronous work should handle failures.

    Example:

    javascript
    useEffect(() => {
    let ignore = false;
    async function loadProducts() {
    try {
    setLoading(true);
    setError(null);
    const response = await fetch("/api/products");
    if (!response.ok) {
    throw new Error("Request failed");
    }
    const data = await response.json();
    if (!ignore) {
    setProducts(data);
    }
    } catch (error) {
    if (!ignore) {
    setError(error);
    }
    } finally {
    if (!ignore) {
    setLoading(false);
    }
    }
    }
    loadProducts();
    return () => {
    ignore = true;
    };
    }, []);

    This provides:

    • loading state
    • success state
    • error state
    • cleanup protection

    AbortController

    For fetch requests, an AbortController can actively cancel the request.

    javascript
    useEffect(() => {
    const controller = new AbortController();
    async function loadProducts() {
    try {
    const response = await fetch(
    "/api/products",
    {
    signal: controller.signal,
    }
    );
    const data = await response.json();
    setProducts(data);
    } catch (error) {
    if (error.name !== "AbortError") {
    setError(error);
    }
    }
    }
    loadProducts();
    return () => {
    controller.abort();
    };
    }, []);

    This is useful when an in-flight request should be cancelled as the Effect is cleaned up.

    Race Conditions

    Race conditions can happen when multiple requests overlap.

    Suppose:

    text
    User searches "rea"
    ↓
    Request A starts
    User quickly searches "react"
    ↓
    Request B starts

    If Request A finishes after Request B, it could overwrite the newer results.

    A cleanup strategy or request cancellation can prevent stale results from winning.

    For example:

    javascript
    useEffect(() => {
    const controller = new AbortController();
    async function search() {
    const response = await fetch(
    `/api/search?q=${encodeURIComponent(query)}`,
    {
    signal: controller.signal,
    }
    );
    const data = await response.json();
    setResults(data);
    }
    if (query) {
    search();
    }
    return () => {
    controller.abort();
    };
    }, [query]);

    Each new query cleans up the previous request.

    Browser Event Listeners

    Effects are useful for subscribing to browser events.

    javascript
    useEffect(() => {
    function handleOnline() {
    setIsOnline(true);
    }
    function handleOffline() {
    setIsOnline(false);
    }
    window.addEventListener(
    "online",
    handleOnline
    );
    window.addEventListener(
    "offline",
    handleOffline
    );
    return () => {
    window.removeEventListener(
    "online",
    handleOnline
    );
    window.removeEventListener(
    "offline",
    handleOffline
    );
    };
    }, []);

    The Effect owns the subscription lifecycle.

    Timers

    Timers are another common side effect.

    javascript
    useEffect(() => {
    const timer = setInterval(() => {
    setSeconds((value) => value + 1);
    }, 1000);
    return () => {
    clearInterval(timer);
    };
    }, []);

    The cleanup prevents the interval from continuing after the component is no longer using it.

    Local Storage

    An Effect can synchronize React state with browser storage.

    javascript
    const [theme, setTheme] = useState("light");
    useEffect(() => {
    localStorage.setItem(
    "theme",
    theme
    );
    }, [theme]);

    The relationship is:

    text
    theme changes
    ↓
    render
    ↓
    Effect
    ↓
    localStorage updated

    If localStorage is used to initialize state, lazy state initialization can avoid unnecessary repeated reads:

    javascript
    const [theme, setTheme] = useState(() => {
    return localStorage.getItem("theme") ?? "light";
    });

    WebSockets

    A WebSocket connection is an external system that needs setup and cleanup.

    javascript
    useEffect(() => {
    const socket = new WebSocket(
    `wss://example.com/rooms/${roomId}`
    );
    socket.addEventListener(
    "message",
    handleMessage
    );
    return () => {
    socket.removeEventListener(
    "message",
    handleMessage
    );
    socket.close();
    };
    }, [roomId]);

    When roomId changes:

    text
    Old room
    ↓
    cleanup
    ↓
    close old socket
    ↓
    setup
    ↓
    connect new room

    This is exactly the kind of synchronization that Effects are designed for.

    Third-Party Libraries

    Suppose a chart library needs a DOM element.

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

    The component owns the DOM element.

    The Effect synchronizes the third-party library with React data.

    Cleanup destroys the external instance.

    Effect Dependencies

    Dependencies should represent the reactive values used by the Effect.

    Example:

    javascript
    useEffect(() => {
    connect(roomId, userId);
    }, [roomId, userId]);

    Both roomId and userId affect the synchronization.

    If one changes, the Effect should be reconsidered.

    Avoid deliberately removing dependencies just to prevent an Effect from running.

    Instead, restructure the logic when necessary.

    Stale Closures

    A stale closure occurs when an Effect or callback captures an older value.

    For example:

    javascript
    useEffect(() => {
    const timer = setInterval(() => {
    console.log(count);
    }, 1000);
    return () => clearInterval(timer);
    }, []);

    The Effect captures the value of count from the render where the Effect was created.

    If the Effect needs the latest count, its dependency relationship must be designed correctly.

    One option:

    javascript
    useEffect(() => {
    const timer = setInterval(() => {
    console.log(count);
    }, 1000);
    return () => clearInterval(timer);
    }, [count]);

    Another option may be to use a functional state update when the actual requirement is to update state from the previous value:

    javascript
    setCount((value) => value + 1);

    The correct solution depends on what the Effect is trying to synchronize.

    Effects and Refs

    Refs are useful when an Effect needs to interact with a DOM node or hold a mutable value.

    Example:

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

    The responsibilities are clear:

    text
    useRef
    ↓
    access DOM element
    useEffect
    ↓
    perform synchronization after commit

    Effect Cleanup Timing

    When dependencies change, React cleans up the previous Effect before running the new setup for the updated dependencies.

    Conceptually:

    text
    Old Effect
    ↓
    cleanup
    ↓
    New Effect

    When the component is removed:

    text
    Component unmount
    ↓
    Effect cleanup

    This is why cleanup should undo the external work created by that particular Effect.

    Strict Mode and Effects

    In development, React Strict Mode can intentionally run an additional setup/cleanup cycle for Effects.

    For example:

    text
    setup
    cleanup
    setup

    This helps expose Effects that are not correctly reversible.

    If an Effect creates a subscription:

    javascript
    useEffect(() => {
    subscribe();
    return () => {
    unsubscribe();
    };
    }, []);

    the setup and cleanup should work correctly even when React exercises them more than once during development.

    Do not add hacks simply to hide Strict Mode behavior.

    Instead, make the Effect's setup and cleanup correct.

    Effect Idempotency

    Good Effects should be safe to start and clean up according to their lifecycle.

    For example:

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

    Each setup creates one connection.

    Each cleanup disconnects that connection.

    This makes the lifecycle predictable.

    Avoid Unnecessary Effects

    A component does not need an Effect merely because something happens after rendering.

    For example, this is unnecessary:

    javascript
    const [fullName, setFullName] = useState("");
    useEffect(() => {
    setFullName(`${firstName} ${lastName}`);
    }, [firstName, lastName]);

    Prefer:

    javascript
    const fullName = `${firstName} ${lastName}`;

    Similarly, if a button click should trigger an action:

    javascript
    function handleSave() {
    saveData();
    }

    do not create an Effect just to watch a state variable representing the click.

    Event Logic vs Effect Logic

    Consider a purchase button.

    Incorrect approach:

    javascript
    const [purchased, setPurchased] = useState(false);
    useEffect(() => {
    if (purchased) {
    sendPurchaseAnalytics();
    }
    }, [purchased]);
    function handleClick() {
    setPurchased(true);
    }

    If the analytics event is specifically caused by the button click, the event handler can often express that relationship more directly:

    javascript
    function handleClick() {
    setPurchased(true);
    sendPurchaseAnalytics();
    }

    Effects are best when the external system needs to stay synchronized with state or props, rather than when they are being used as a generic event system.

    Effects and Derived Values

    Avoid:

    javascript
    const [total, setTotal] = useState(0);
    useEffect(() => {
    setTotal(
    cart.reduce(
    (sum, item) =>
    sum + item.price * item.quantity,
    0
    )
    );
    }, [cart]);

    Prefer:

    javascript
    const total = cart.reduce(
    (sum, item) =>
    sum + item.price * item.quantity,
    0
    );

    If the calculation is expensive, consider useMemo:

    javascript
    const total = useMemo(() => {
    return cart.reduce(
    (sum, item) =>
    sum + item.price * item.quantity,
    0
    );
    }, [cart]);

    The key distinction is:

    text
    Derived value
    ↓
    calculate during render
    External synchronization
    ↓
    Effect

    Effects and State Machines

    Complex asynchronous behavior can become easier to reason about when state transitions are explicit.

    For example:

    javascript
    const initialState = {
    status: "idle",
    data: null,
    error: null,
    };

    A reducer can manage:

    text
    requestStarted
    requestSucceeded
    requestFailed

    An Effect can perform the external request:

    text
    Effect
    ↓
    API request
    ↓
    dispatch(action)
    ↓
    reducer
    ↓
    new state

    This separates:

    • external synchronization
    • state transition logic

    and works especially well with useReducer.

    Custom Hooks for Side Effects

    When side-effect logic becomes reusable, extract it into a Custom Hook.

    For example:

    javascript
    function useOnlineStatus() {
    const [isOnline, setIsOnline] = useState(
    navigator.onLine
    );
    useEffect(() => {
    function handleOnline() {
    setIsOnline(true);
    }
    function handleOffline() {
    setIsOnline(false);
    }
    window.addEventListener(
    "online",
    handleOnline
    );
    window.addEventListener(
    "offline",
    handleOffline
    );
    return () => {
    window.removeEventListener(
    "online",
    handleOnline
    );
    window.removeEventListener(
    "offline",
    handleOffline
    );
    };
    }, []);
    return isOnline;
    }

    Now components can simply write:

    javascript
    const isOnline = useOnlineStatus();

    This keeps component code focused on UI.

    A Practical Decision Framework

    When you need to perform some logic, ask:

    Question 1: Is it a pure calculation?

    If yes, calculate it during rendering.

    javascript
    const total = price * quantity;

    Question 2: Is it caused directly by a user interaction?

    If yes, use an event handler.

    javascript
    function handleClick() {
    submitForm();
    }

    Question 3: Does React state or props need to stay synchronized with an external system?

    If yes, consider an Effect.

    javascript
    useEffect(() => {
    externalSystem.update(value);
    }, [value]);

    Question 4: Does it need setup and cleanup?

    If yes, an Effect is often appropriate.

    javascript
    useEffect(() => {
    subscribe();
    return () => {
    unsubscribe();
    };
    }, []);

    Question 5: Is the logic reused across components?

    If yes, consider a Custom Hook.

    Common Mistakes

    1. Using Effects for Everything

    Effects are not a replacement for event handlers or normal calculations.

    2. Missing Dependencies

    If an Effect uses a reactive value, its dependency relationship needs to be represented correctly.

    3. Suppressing Dependency Warnings

    Avoid silencing lint warnings without understanding the underlying dependency problem.

    Often the better solution is to restructure the Effect.

    4. Forgetting Cleanup

    Subscriptions, listeners, timers, and external resources often require cleanup.

    5. Updating State in an Unnecessary Effect

    Avoid creating state solely for values that can be derived.

    6. Creating Effect Chains

    Prefer direct calculations and explicit state transitions over long chains of Effects.

    7. Ignoring Race Conditions

    Asynchronous Effects can overlap.

    Use cancellation or cleanup strategies when older work must not overwrite newer results.

    8. Assuming [] Means "Never Again"

    An empty dependency array means the Effect has no changing dependencies. Development Strict Mode may still exercise setup and cleanup more than once.

    9. Performing Side Effects During Rendering

    Avoid:

    javascript
    function Component() {
    document.title = "Dashboard";
    return <h1>Dashboard</h1>;
    }

    Prefer:

    javascript
    function Component() {
    useEffect(() => {
    document.title = "Dashboard";
    }, []);
    return <h1>Dashboard</h1>;
    }

    Best Practices

    1. Keep Rendering Pure

    Rendering should primarily calculate UI.

    2. Use Event Handlers for User Actions

    If a user clicks a button, the handler is usually the clearest place for the resulting action.

    3. Use Effects for External Synchronization

    Use Effects for subscriptions, browser APIs, network synchronization, and similar external systems.

    4. Keep Effects Small

    One Effect should ideally represent one coherent synchronization concern.

    5. Always Clean Up What You Set Up

    If an Effect creates:

    text
    listener
    timer
    subscription
    connection
    observer
    external instance

    consider how and when it should be cleaned up.

    6. Keep Dependencies Accurate

    Do not treat dependency arrays as a performance switch.

    They describe the reactive values the Effect depends on.

    7. Avoid Derived State

    Calculate values during rendering whenever possible.

    8. Handle Async Work Carefully

    Consider:

    • errors
    • cancellation
    • race conditions
    • loading states
    • stale results

    9. Extract Reusable Effect Logic

    Use Custom Hooks when the same side-effect behavior appears in multiple components.

    10. Test Behavior, Not Implementation Details

    Verify observable outcomes such as:

    • subscriptions are created
    • cleanup happens
    • requests are cancelled when appropriate
    • state updates correctly
    • external systems receive the expected values

    Practical Example: Complete Search Component

    javascript
    import {
    useEffect,
    useState,
    } from "react";
    function Search() {
    const [query, setQuery] = useState("");
    const [results, setResults] = useState([]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);
    useEffect(() => {
    if (!query.trim()) {
    setResults([]);
    setLoading(false);
    setError(null);
    return;
    }
    const controller = new AbortController();
    async function search() {
    try {
    setLoading(true);
    setError(null);
    const response = await fetch(
    `/api/search?q=${encodeURIComponent(query)}`,
    {
    signal: controller.signal,
    }
    );
    if (!response.ok) {
    throw new Error("Search failed");
    }
    const data = await response.json();
    setResults(data);
    } catch (error) {
    if (error.name !== "AbortError") {
    setError(error);
    }
    } finally {
    if (!controller.signal.aborted) {
    setLoading(false);
    }
    }
    }
    search();
    return () => {
    controller.abort();
    };
    }, [query]);
    return (
    <div>
    <input
    value={query}
    onChange={(event) =>
    setQuery(event.target.value)
    }
    placeholder="Search..."
    />
    {loading && <p>Searching...</p>}
    {error && (
    <p>Unable to complete the search.</p>
    )}
    <ul>
    {results.map((result) => (
    <li key={result.id}>
    {result.name}
    </li>
    ))}
    </ul>
    </div>
    );
    }
    export default Search;

    This example demonstrates:

    • state
    • Effect synchronization
    • dependency tracking
    • request cancellation
    • error handling
    • loading state
    • cleanup
    • conditional rendering

    Practical Example: Subscription Management

    javascript
    function ChatRoom({ roomId }) {
    const [messages, setMessages] = useState([]);
    useEffect(() => {
    const connection = createChatConnection(roomId);
    function handleMessage(message) {
    setMessages((current) => [
    ...current,
    message,
    ]);
    }
    connection.on("message", handleMessage);
    connection.connect();
    return () => {
    connection.off("message", handleMessage);
    connection.disconnect();
    };
    }, [roomId]);
    return (
    <div>
    {messages.map((message) => (
    <p key={message.id}>
    {message.text}
    </p>
    ))}
    </div>
    );
    }

    When roomId changes:

    text
    Old room
    ↓
    remove listener
    ↓
    disconnect
    ↓
    new room
    ↓
    connect
    ↓
    subscribe

    This is a textbook synchronization pattern.

    Practice Exercises

    Exercise 1: Document Title

    Create a component that keeps:

    text
    document.title

    synchronized with a count state.

    Exercise 2: Window Resize

    Build a component that tracks:

    text
    window.innerWidth
    window.innerHeight

    Use an Effect and clean up the resize listener.

    Exercise 3: Timer

    Build a timer that:

    • starts an interval
    • increments elapsed seconds
    • cleans up the interval
    • can be started and stopped

    Exercise 4: Local Storage

    Synchronize a theme setting with:

    text
    localStorage

    Initialize the state from storage and save changes back to storage.

    Exercise 5: API Request

    Build a product component that:

    • fetches products
    • displays loading
    • handles errors
    • displays results
    • cancels stale requests

    Exercise 6: WebSocket

    Create a Chat Room component that:

    • connects to a room
    • listens for messages
    • displays messages
    • disconnects when the room changes or component unmounts

    Exercise 7: Refactor an Unnecessary Effect

    Given:

    javascript
    const [total, setTotal] = useState(0);
    useEffect(() => {
    setTotal(
    cart.reduce(
    (sum, item) =>
    sum + item.price * item.quantity,
    0
    )
    );
    }, [cart]);

    Refactor it so total is derived without an Effect.

    Mini Project: Online Dashboard

    Build an Online Dashboard that combines multiple side-effect patterns.

    Requirements:

    1. Online Status

    Display:

    text
    Online
    Offline

    Use browser online/offline events.

    2. Document Title

    Keep the title synchronized:

    text
    Dashboard - Online
    Dashboard - Offline

    3. Data Fetching

    Fetch dashboard data from an API.

    Handle:

    text
    Loading
    Success
    Error

    4. Window Size

    Display the current browser dimensions.

    5. Cleanup

    Correctly clean up:

    • event listeners
    • timers if used
    • pending requests
    • subscriptions

    Suggested architecture:

    text
    Dashboard
    │
    ├── useOnlineStatus()
    │
    ├── useEffect()
    │ └── document.title
    │
    ├── useEffect()
    │ └── API request
    │
    └── useWindowSize()

    This project demonstrates how multiple independent side-effect concerns can coexist without turning the component into one giant Effect.

    Interview questions & answers

    Interview preparation

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

    Beginner

    3
    1. 1What is a side effect in React?
      Beginner

      Model answer

      A side effect is an interaction with something outside the component's rendering calculation, such as an API, browser API, timer, subscription, storage system, or third-party library.

    2. 2Why does an Effect need cleanup?
      Beginner

      Model answer

      Cleanup prevents resources created by the Effect from continuing after the synchronization is no longer needed. Examples: remove event listener → clear timer → unsubscribe → disconnect WebSocket → destroy library instance → cancel request
    3. 3Why should you avoid derived state in Effects?
      Beginner

      Model answer

      Because derived values can usually be calculated directly during rendering.

      Using an Effect introduces an unnecessary extra state update and can create synchronization complexity.

    Intermediate

    3
    1. 4When should you use useEffect?
      Intermediate

      Model answer

      Use useEffect when a component needs to synchronize with an external system. Examples include:
      • subscriptions
      • browser APIs
      • network synchronization
      • timers
      • third-party libraries
    2. 5What does the dependency array do?
      Intermediate

      Model answer

      It describes the reactive values that the Effect depends on. Example: useEffect(() => { → connect(roomId); → }, [roomId]); The Effect is reconsidered when roomId changes.
    3. 6What is a stale closure?
      Intermediate

      Model answer

      A stale closure occurs when a callback or Effect uses values captured from an older render rather than the latest values.

      Correct dependencies, functional state updates, refs, or restructuring the logic can solve different stale-value scenarios.

    Advanced

    3
    1. 7Should every asynchronous operation use useEffect?
      Advanced

      Model answer

      No.

      The correct location depends on what causes the operation.

      If it is directly caused by a user interaction, an event handler may be more appropriate.

      If it synchronizes with changing props or state, an Effect may be appropriate.

    2. 8What is an Effect cleanup function?
      Advanced

      Model answer

      It is the function returned from the Effect: useEffect(() => { → subscribe(); → return () => { → unsubscribe(); → }; → }, []); React uses it to clean up the previous synchronization.
    3. 9What happens to Effects in Strict Mode?
      Advanced

      Model answer

      In development, Strict Mode may intentionally run an additional setup/cleanup cycle to help identify Effects that do not correctly handle their lifecycle.

    Side Effects Cheat Sheet

    Synchronize document title

    javascript
    useEffect(() => {
    document.title = title;
    }, [title]);

    Subscribe

    javascript
    useEffect(() => {
    subscribe();
    return () => {
    unsubscribe();
    };
    }, []);

    Event listener

    javascript
    useEffect(() => {
    window.addEventListener(
    "resize",
    handleResize
    );
    return () => {
    window.removeEventListener(
    "resize",
    handleResize
    );
    };
    }, []);

    Timer

    javascript
    useEffect(() => {
    const timer = setInterval(callback, 1000);
    return () => {
    clearInterval(timer);
    };
    }, []);

    Fetch with cancellation

    javascript
    useEffect(() => {
    const controller = new AbortController();
    fetch(url, {
    signal: controller.signal,
    });
    return () => {
    controller.abort();
    };
    }, [url]);

    Derived value

    Avoid:

    javascript
    useEffect(() => {
    setTotal(calculateTotal(items));
    }, [items]);

    Prefer:

    javascript
    const total = calculateTotal(items);

    User interaction

    Prefer:

    javascript
    function handleClick() {
    submitForm();
    }

    rather than using an Effect to detect that a click happened.

    Key Takeaways

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