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
useStateoruseReducer). - 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
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);
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]);
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>
);
}
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>
);
}
5. Hands-On Practice Challenges
Master memoization cache mechanics, shallow comparison algorithms, and transition priority scheduling. Click "Solve Challenge" to open the workspace!
Challenge 1: Recreate useMemo Computation Cache
Write a memoization utility memoizeComputation(computeFn) that caches the result of expensive calculations based on arguments.
- 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.
[Computing...] 2500 | [Cache Hit] Serving cached result for 5 -> 2500
memoizeComputation to cache return values.
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.
- Check reference equality with
Object.is(objA, objB). - Compare key counts and each property shallowly.
- Return true if identical, false if any property changed.
Equal: true | Not Equal: false
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.
- Execute
urgentTask()synchronously. - Queue
nonUrgentTask()usingqueueMicrotask.
[Urgent Phase 1]: Input Keypress... | [Non-Urgent Phase 2]: Filtered 10,000 table rows
priorityScheduler using queueMicrotask.