React

Chapter 14: Performance & Advanced Concepts — React.memo, useMemo, useCallback, code splitting with React.lazy and Suspense, and React 18 useTransition.

1. What Triggers a Re-Render in React?

In React, a component re-renders when:

  • 1. Its own internal state changes (via useState or useReducer).
  • 2. A Context value it consumes is updated by a Provider.
  • 3. Its Parent component re-renders (by default, all child components re-render automatically when a parent re-renders!).

2. The Memoization Trifecta

01

React.memo(Component)

Prevents Child Re-renders

Skips rendering the child component if its incoming props are shallowly equal to the previous render.

export const HeavyList = 
    React.memo(ListComponent);
02

useMemo(() => fn, [deps])

Caches Heavy Calculations

Caches the return value of an expensive calculation (e.g. sorting 50,000 items) so it only recalculates when dependencies change.

const total = useMemo(() => 
    computeGrandTotal(items), [items]);
03

useCallback(fn, [deps])

Caches Function References

Preserves the same memory reference for a callback function passed down to a React.memo child to prevent breaking shallow equality.

const onDelete = useCallback((id) => 
    deleteItem(id), []);

3. Code Splitting with React.lazy() & <Suspense>

Instead of forcing users to download a massive 5MB JavaScript bundle on initial page load, split routes and heavy modal dialogs into separate on-demand chunks:

import React, { Suspense } from "react";

// Dynamically load chunk only when requested by router:
const AnalyticsDashboard = React.lazy(() => import("./features/analytics/Dashboard"));

function App() {
    return (
        <div>
            <Navbar />
            <Suspense fallback={<div className="page-loader">Loading Dashboard...</div>}>
                <AnalyticsDashboard />
            </Suspense>
        </div>
    );
}
Code-Split Network Performance Output:
[Main Bundle] 48 KB (Initial Page Load in 85ms!) [Route Changed -> /analytics] Loading Analytics.chunk.js (120 KB on-demand) [Suspense] Rendered fallback loader -> Seamlessly switched to Dashboard on download complete!

4. React 18 Concurrent Rendering: useTransition

In React 18, useTransition lets you mark state updates as non-urgent transitions. Urgent updates (typing in an input) remain instantaneous, while non-urgent updates (filtering a 10,000-row table) can be interrupted without freezing the browser UI:

function SearchTable({ allItems }) {
    const [searchTerm, setSearchTerm] = React.useState("");
    const [filteredList, setFilteredList] = React.useState(allItems);
    const [isPending, startTransition] = React.useTransition();

    function handleSearch(e) {
        const query = e.target.value;
        setSearchTerm(query); // 1. Urgent: update input immediately

        startTransition(() => {
            // 2. Non-Urgent Transition: filter huge dataset in background
            const results = allItems.filter(item => item.name.includes(query));
            setFilteredList(results);
        });
    }

    return (
        <div>
            <input value={searchTerm} onChange={handleSearch} placeholder="Search..." />
            {isPending && <span>Filtering list in background...</span>}
        </div>
    );
}
Concurrent Rendering Output:
⚡ isPending: false (60fps responsive)

5. Hands-On Practice Challenges

Master memoization cache mechanics, shallow comparison algorithms, and transition priority scheduling. Click "Solve Challenge" to open the workspace!

🎯 Hands-on Coding Challenge Memoization Engine

Challenge 1: Recreate useMemo Computation Cache

Write a memoization utility memoizeComputation(computeFn) that caches the result of expensive calculations based on arguments.

📋 Requirements & What to Write:
  • Store past results in a Map cache.
  • If the argument has been computed before, return cached value.
  • Otherwise invoke computeFn(arg) and cache the result.
Expected Result: [Computing...] 2500 | [Cache Hit] Serving cached result for 5 -> 2500
💡
Editor Instructions: Complete memoizeComputation to cache return values.
memoize.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
🎯 Hands-on Coding Challenge React.memo Equality

Challenge 2: React.memo Shallow Equality Comparator

Implement shallowEqual(objA, objB) that replicates how React.memo compares previous props with new props to decide whether to skip re-rendering.

📋 Requirements & What to Write:
  • Check reference equality with Object.is(objA, objB).
  • Compare key counts and each property shallowly.
  • Return true if identical, false if any property changed.
Expected Result: Equal: true | Not Equal: false
💡
Editor Instructions: Implement shallow comparison algorithm for props objects.
shallowEqual.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...
'Filtered 10,000 table rows' );">
🎯 Hands-on Coding Challenge Concurrent Scheduler

Challenge 3: Urgent vs Non-Urgent Priority Scheduler

Write an execution scheduler priorityScheduler(urgentTask, nonUrgentTask) that executes urgent UI updates immediately and defers non-urgent transitions to the microtask queue.

📋 Requirements & What to Write:
  • Execute urgentTask() synchronously.
  • Queue nonUrgentTask() using queueMicrotask.
Expected Result: [Urgent Phase 1]: Input Keypress... | [Non-Urgent Phase 2]: Filtered 10,000 table rows
💡
Editor Instructions: Complete priorityScheduler using queueMicrotask.
priorityScheduler.js
Console / Terminal Output
// Output will appear here when you click "Run Code"...