- Spec files
npm-react
Describes: pkg:npm/react@19.1.x
- Description
- React is a JavaScript library for building user interfaces with a declarative, component-based approach.
- Author
- tessl
- Last updated
performance.md docs/
1# Performance Optimization23Tools for optimizing React application performance through memoization, lazy loading, and caching. These features help prevent unnecessary re-renders and defer expensive operations.45## Capabilities67### memo89Higher-order component that memoizes component renders based on props comparison.1011```typescript { .api }12/**13* Memoizes a component to prevent unnecessary re-renders14* @param Component - Function component to memoize15* @param propsAreEqual - Optional custom comparison function16* @returns Memoized component17*/18function memo<P extends object>(19Component: React.FunctionComponent<P>,20propsAreEqual?: (prevProps: P, nextProps: P) => boolean21): React.NamedExoticComponent<P>;22```2324**Usage Examples:**2526```typescript27import React, { memo, useState } from "react";2829interface UserCardProps {30name: string;31email: string;32age: number;33}3435// Basic memoization - re-renders only when props change36const UserCard = memo<UserCardProps>(({ name, email, age }) => {37console.log("UserCard rendered");38return (39<div className="user-card">40<h3>{name}</h3>41<p>{email}</p>42<p>Age: {age}</p>43</div>44);45});4647// Custom comparison function48const UserCardWithCustomComparison = memo<UserCardProps>(49({ name, email, age }) => (50<div className="user-card">51<h3>{name}</h3>52<p>{email}</p>53<p>Age: {age}</p>54</div>55),56(prevProps, nextProps) => {57// Only re-render if name or email changes, ignore age changes58return prevProps.name === nextProps.name &&59prevProps.email === nextProps.email;60}61);6263function App() {64const [count, setCount] = useState(0);65const [user] = useState({ name: "Alice", email: "alice@example.com", age: 30 });6667return (68<div>69<button onClick={() => setCount(count + 1)}>70Count: {count}71</button>72{/* UserCard won't re-render when count changes */}73<UserCard {...user} />74</div>75);76}77```7879### useMemo8081Hook for memoizing expensive calculations (covered in detail in Hooks documentation).8283```typescript { .api }84/**85* Memoizes expensive calculations86* @param factory - Function that returns the computed value87* @param deps - Dependency array88* @returns Memoized value89*/90function useMemo<T>(factory: () => T, deps: React.DependencyList | undefined): T;91```9293**Usage Examples:**9495```typescript96import React, { useMemo, useState } from "react";9798function ExpensiveList({ items }: { items: number[] }) {99const [filter, setFilter] = useState("");100101// Expensive calculation - only recalculates when items or filter changes102const filteredAndSortedItems = useMemo(() => {103console.log("Calculating filtered and sorted items");104return items105.filter(item => item.toString().includes(filter))106.sort((a, b) => b - a); // Sort descending107}, [items, filter]);108109// Expensive formatting - only recalculates when filteredAndSortedItems changes110const formattedItems = useMemo(() => {111console.log("Formatting items");112return filteredAndSortedItems.map(item => ({113id: item,114display: `Item #${item}`,115category: item % 2 === 0 ? "even" : "odd"116}));117}, [filteredAndSortedItems]);118119return (120<div>121<input122type="text"123placeholder="Filter items..."124value={filter}125onChange={(e) => setFilter(e.target.value)}126/>127<ul>128{formattedItems.map(item => (129<li key={item.id}>130{item.display} ({item.category})131</li>132))}133</ul>134</div>135);136}137```138139### useCallback140141Hook for memoizing callback functions (covered in detail in Hooks documentation).142143```typescript { .api }144/**145* Memoizes callback functions146* @param callback - Function to memoize147* @param deps - Dependency array148* @returns Memoized callback function149*/150function useCallback<T extends (...args: any[]) => any>(151callback: T,152deps: React.DependencyList153): T;154```155156**Usage Examples:**157158```typescript159import React, { useCallback, useState, memo } from "react";160161interface ButtonProps {162onClick: () => void;163children: React.ReactNode;164}165166const ExpensiveButton = memo<ButtonProps>(({ onClick, children }) => {167console.log("ExpensiveButton rendered");168return <button onClick={onClick}>{children}</button>;169});170171function TodoApp() {172const [todos, setTodos] = useState<string[]>([]);173const [inputValue, setInputValue] = useState("");174175// Memoized callback - only recreated when todos changes176const addTodo = useCallback(() => {177if (inputValue.trim()) {178setTodos(currentTodos => [...currentTodos, inputValue]);179setInputValue("");180}181}, [inputValue]); // Dependencies: inputValue182183// Memoized callback - never recreated (empty dependency array)184const clearTodos = useCallback(() => {185setTodos([]);186}, []);187188// Memoized callback factory189const createRemoveTodo = useCallback((index: number) => {190return () => {191setTodos(currentTodos => currentTodos.filter((_, i) => i !== index));192};193}, []);194195return (196<div>197<input198value={inputValue}199onChange={(e) => setInputValue(e.target.value)}200placeholder="Add todo..."201/>202203{/* These buttons won't cause unnecessary re-renders */}204<ExpensiveButton onClick={addTodo}>Add Todo</ExpensiveButton>205<ExpensiveButton onClick={clearTodos}>Clear All</ExpensiveButton>206207<ul>208{todos.map((todo, index) => (209<li key={index}>210{todo}211<ExpensiveButton onClick={createRemoveTodo(index)}>212Remove213</ExpensiveButton>214</li>215))}216</ul>217</div>218);219}220```221222### lazy223224Creates lazily loaded components for code splitting.225226```typescript { .api }227/**228* Creates a lazily loaded component229* @param factory - Function that returns a Promise resolving to component230* @returns Lazy component that loads on demand231*/232function lazy<T extends React.ComponentType<any>>(233factory: () => Promise<{ default: T }>234): React.LazyExoticComponent<T>;235```236237**Usage Examples:**238239```typescript240import React, { lazy, Suspense, useState } from "react";241242// Lazily load components243const HeavyChart = lazy(() => import("./HeavyChart"));244const UserDashboard = lazy(() => import("./UserDashboard"));245const AdminPanel = lazy(() => import("./AdminPanel"));246247function App() {248const [currentView, setCurrentView] = useState<"chart" | "dashboard" | "admin" | null>(null);249250const renderView = () => {251switch (currentView) {252case "chart":253return <HeavyChart />;254case "dashboard":255return <UserDashboard />;256case "admin":257return <AdminPanel />;258default:259return <div>Select a view</div>;260}261};262263return (264<div>265<nav>266<button onClick={() => setCurrentView("chart")}>Chart</button>267<button onClick={() => setCurrentView("dashboard")}>Dashboard</button>268<button onClick={() => setCurrentView("admin")}>Admin</button>269</nav>270271<main>272<Suspense fallback={<div>Loading...</div>}>273{renderView()}274</Suspense>275</main>276</div>277);278}279```280281### cache282283Caches function results across renders (React 19+ feature).284285```typescript { .api }286/**287* Caches function results across renders288* @param fn - Function to cache289* @returns Cached version of the function290*/291function cache<A extends ReadonlyArray<unknown>, T>(292fn: (...args: A) => T293): (...args: A) => T;294```295296**Usage Examples:**297298```typescript299import React, { cache } from "react";300301// Cache expensive data processing302const processUserData = cache((userData: any[]) => {303console.log("Processing user data...");304return userData.map(user => ({305...user,306displayName: `${user.firstName} ${user.lastName}`,307isActive: user.lastLogin && Date.now() - user.lastLogin < 30 * 24 * 60 * 60 * 1000308}));309});310311// Cache API calls312const fetchUserById = cache(async (userId: string) => {313console.log(`Fetching user ${userId}...`);314const response = await fetch(`/api/users/${userId}`);315return response.json();316});317318function UserList({ users }: { users: any[] }) {319// Will use cached result if users array hasn't changed320const processedUsers = processUserData(users);321322return (323<ul>324{processedUsers.map(user => (325<li key={user.id}>326{user.displayName} - {user.isActive ? "Active" : "Inactive"}327</li>328))}329</ul>330);331}332```333334## Advanced Performance Patterns335336### Virtualization Pattern337338```typescript339import React, { useMemo, useState, useRef, useLayoutEffect } from "react";340341interface VirtualizedListProps {342items: any[];343itemHeight: number;344containerHeight: number;345renderItem: (item: any, index: number) => React.ReactNode;346}347348function VirtualizedList({ items, itemHeight, containerHeight, renderItem }: VirtualizedListProps) {349const [scrollTop, setScrollTop] = useState(0);350const containerRef = useRef<HTMLDivElement>(null);351352const visibleItems = useMemo(() => {353const startIndex = Math.floor(scrollTop / itemHeight);354const endIndex = Math.min(355startIndex + Math.ceil(containerHeight / itemHeight) + 1,356items.length357);358359return items.slice(startIndex, endIndex).map((item, index) => ({360item,361index: startIndex + index362}));363}, [items, itemHeight, containerHeight, scrollTop]);364365const totalHeight = items.length * itemHeight;366const offsetY = Math.floor(scrollTop / itemHeight) * itemHeight;367368return (369<div370ref={containerRef}371style={{ height: containerHeight, overflow: "auto" }}372onScroll={(e) => setScrollTop(e.currentTarget.scrollTop)}373>374<div style={{ height: totalHeight, position: "relative" }}>375<div style={{ transform: `translateY(${offsetY}px)` }}>376{visibleItems.map(({ item, index }) => (377<div key={index} style={{ height: itemHeight }}>378{renderItem(item, index)}379</div>380))}381</div>382</div>383</div>384);385}386```387388### Debounced Input Pattern389390```typescript391import React, { useState, useMemo, useCallback } from "react";392393function useDebounce<T>(value: T, delay: number): T {394const [debouncedValue, setDebouncedValue] = useState(value);395396React.useEffect(() => {397const handler = setTimeout(() => {398setDebouncedValue(value);399}, delay);400401return () => {402clearTimeout(handler);403};404}, [value, delay]);405406return debouncedValue;407}408409function SearchableList({ items }: { items: string[] }) {410const [searchTerm, setSearchTerm] = useState("");411const debouncedSearchTerm = useDebounce(searchTerm, 300);412413// Expensive filtering only happens after debounce delay414const filteredItems = useMemo(() => {415console.log("Filtering items...");416return items.filter(item =>417item.toLowerCase().includes(debouncedSearchTerm.toLowerCase())418);419}, [items, debouncedSearchTerm]);420421return (422<div>423<input424type="text"425value={searchTerm}426onChange={(e) => setSearchTerm(e.target.value)}427placeholder="Search items..."428/>429<ul>430{filteredItems.map((item, index) => (431<li key={index}>{item}</li>432))}433</ul>434</div>435);436}437```438439## Types440441### Performance-Related Types442443```typescript { .api }444interface NamedExoticComponent<P = {}> extends ExoticComponent<P> {445displayName?: string;446}447448interface ExoticComponent<P = {}> {449(props: P): ReactElement | null;450readonly $$typeof: symbol;451}452453interface LazyExoticComponent<T extends ComponentType<any>> extends ExoticComponent<ComponentPropsWithRef<T>> {454readonly _result: T;455}456457type ComponentPropsWithRef<T extends ElementType> = T extends ComponentClass<infer P>458? PropsWithoutRef<P> & RefAttributes<InstanceType<T>>459: PropsWithRef<ComponentProps<T>>;460461type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =462T extends JSXElementConstructor<infer P>463? P464: T extends keyof JSX.IntrinsicElements465? JSX.IntrinsicElements[T]466: {};467```