npm-react

Description
React is a JavaScript library for building user interfaces with a declarative, component-based approach.
Author
tessl
Last updated

How to use

npx @tessl/cli registry install tessl/npm-react@19.1.0

performance.md docs/

1
# Performance Optimization
2
3
Tools for optimizing React application performance through memoization, lazy loading, and caching. These features help prevent unnecessary re-renders and defer expensive operations.
4
5
## Capabilities
6
7
### memo
8
9
Higher-order component that memoizes component renders based on props comparison.
10
11
```typescript { .api }
12
/**
13
* Memoizes a component to prevent unnecessary re-renders
14
* @param Component - Function component to memoize
15
* @param propsAreEqual - Optional custom comparison function
16
* @returns Memoized component
17
*/
18
function memo<P extends object>(
19
Component: React.FunctionComponent<P>,
20
propsAreEqual?: (prevProps: P, nextProps: P) => boolean
21
): React.NamedExoticComponent<P>;
22
```
23
24
**Usage Examples:**
25
26
```typescript
27
import React, { memo, useState } from "react";
28
29
interface UserCardProps {
30
name: string;
31
email: string;
32
age: number;
33
}
34
35
// Basic memoization - re-renders only when props change
36
const UserCard = memo<UserCardProps>(({ name, email, age }) => {
37
console.log("UserCard rendered");
38
return (
39
<div className="user-card">
40
<h3>{name}</h3>
41
<p>{email}</p>
42
<p>Age: {age}</p>
43
</div>
44
);
45
});
46
47
// Custom comparison function
48
const 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 changes
58
return prevProps.name === nextProps.name &&
59
prevProps.email === nextProps.email;
60
}
61
);
62
63
function App() {
64
const [count, setCount] = useState(0);
65
const [user] = useState({ name: "Alice", email: "alice@example.com", age: 30 });
66
67
return (
68
<div>
69
<button onClick={() => setCount(count + 1)}>
70
Count: {count}
71
</button>
72
{/* UserCard won't re-render when count changes */}
73
<UserCard {...user} />
74
</div>
75
);
76
}
77
```
78
79
### useMemo
80
81
Hook for memoizing expensive calculations (covered in detail in Hooks documentation).
82
83
```typescript { .api }
84
/**
85
* Memoizes expensive calculations
86
* @param factory - Function that returns the computed value
87
* @param deps - Dependency array
88
* @returns Memoized value
89
*/
90
function useMemo<T>(factory: () => T, deps: React.DependencyList | undefined): T;
91
```
92
93
**Usage Examples:**
94
95
```typescript
96
import React, { useMemo, useState } from "react";
97
98
function ExpensiveList({ items }: { items: number[] }) {
99
const [filter, setFilter] = useState("");
100
101
// Expensive calculation - only recalculates when items or filter changes
102
const filteredAndSortedItems = useMemo(() => {
103
console.log("Calculating filtered and sorted items");
104
return items
105
.filter(item => item.toString().includes(filter))
106
.sort((a, b) => b - a); // Sort descending
107
}, [items, filter]);
108
109
// Expensive formatting - only recalculates when filteredAndSortedItems changes
110
const formattedItems = useMemo(() => {
111
console.log("Formatting items");
112
return filteredAndSortedItems.map(item => ({
113
id: item,
114
display: `Item #${item}`,
115
category: item % 2 === 0 ? "even" : "odd"
116
}));
117
}, [filteredAndSortedItems]);
118
119
return (
120
<div>
121
<input
122
type="text"
123
placeholder="Filter items..."
124
value={filter}
125
onChange={(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
```
138
139
### useCallback
140
141
Hook for memoizing callback functions (covered in detail in Hooks documentation).
142
143
```typescript { .api }
144
/**
145
* Memoizes callback functions
146
* @param callback - Function to memoize
147
* @param deps - Dependency array
148
* @returns Memoized callback function
149
*/
150
function useCallback<T extends (...args: any[]) => any>(
151
callback: T,
152
deps: React.DependencyList
153
): T;
154
```
155
156
**Usage Examples:**
157
158
```typescript
159
import React, { useCallback, useState, memo } from "react";
160
161
interface ButtonProps {
162
onClick: () => void;
163
children: React.ReactNode;
164
}
165
166
const ExpensiveButton = memo<ButtonProps>(({ onClick, children }) => {
167
console.log("ExpensiveButton rendered");
168
return <button onClick={onClick}>{children}</button>;
169
});
170
171
function TodoApp() {
172
const [todos, setTodos] = useState<string[]>([]);
173
const [inputValue, setInputValue] = useState("");
174
175
// Memoized callback - only recreated when todos changes
176
const addTodo = useCallback(() => {
177
if (inputValue.trim()) {
178
setTodos(currentTodos => [...currentTodos, inputValue]);
179
setInputValue("");
180
}
181
}, [inputValue]); // Dependencies: inputValue
182
183
// Memoized callback - never recreated (empty dependency array)
184
const clearTodos = useCallback(() => {
185
setTodos([]);
186
}, []);
187
188
// Memoized callback factory
189
const createRemoveTodo = useCallback((index: number) => {
190
return () => {
191
setTodos(currentTodos => currentTodos.filter((_, i) => i !== index));
192
};
193
}, []);
194
195
return (
196
<div>
197
<input
198
value={inputValue}
199
onChange={(e) => setInputValue(e.target.value)}
200
placeholder="Add todo..."
201
/>
202
203
{/* These buttons won't cause unnecessary re-renders */}
204
<ExpensiveButton onClick={addTodo}>Add Todo</ExpensiveButton>
205
<ExpensiveButton onClick={clearTodos}>Clear All</ExpensiveButton>
206
207
<ul>
208
{todos.map((todo, index) => (
209
<li key={index}>
210
{todo}
211
<ExpensiveButton onClick={createRemoveTodo(index)}>
212
Remove
213
</ExpensiveButton>
214
</li>
215
))}
216
</ul>
217
</div>
218
);
219
}
220
```
221
222
### lazy
223
224
Creates lazily loaded components for code splitting.
225
226
```typescript { .api }
227
/**
228
* Creates a lazily loaded component
229
* @param factory - Function that returns a Promise resolving to component
230
* @returns Lazy component that loads on demand
231
*/
232
function lazy<T extends React.ComponentType<any>>(
233
factory: () => Promise<{ default: T }>
234
): React.LazyExoticComponent<T>;
235
```
236
237
**Usage Examples:**
238
239
```typescript
240
import React, { lazy, Suspense, useState } from "react";
241
242
// Lazily load components
243
const HeavyChart = lazy(() => import("./HeavyChart"));
244
const UserDashboard = lazy(() => import("./UserDashboard"));
245
const AdminPanel = lazy(() => import("./AdminPanel"));
246
247
function App() {
248
const [currentView, setCurrentView] = useState<"chart" | "dashboard" | "admin" | null>(null);
249
250
const renderView = () => {
251
switch (currentView) {
252
case "chart":
253
return <HeavyChart />;
254
case "dashboard":
255
return <UserDashboard />;
256
case "admin":
257
return <AdminPanel />;
258
default:
259
return <div>Select a view</div>;
260
}
261
};
262
263
return (
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>
270
271
<main>
272
<Suspense fallback={<div>Loading...</div>}>
273
{renderView()}
274
</Suspense>
275
</main>
276
</div>
277
);
278
}
279
```
280
281
### cache
282
283
Caches function results across renders (React 19+ feature).
284
285
```typescript { .api }
286
/**
287
* Caches function results across renders
288
* @param fn - Function to cache
289
* @returns Cached version of the function
290
*/
291
function cache<A extends ReadonlyArray<unknown>, T>(
292
fn: (...args: A) => T
293
): (...args: A) => T;
294
```
295
296
**Usage Examples:**
297
298
```typescript
299
import React, { cache } from "react";
300
301
// Cache expensive data processing
302
const processUserData = cache((userData: any[]) => {
303
console.log("Processing user data...");
304
return userData.map(user => ({
305
...user,
306
displayName: `${user.firstName} ${user.lastName}`,
307
isActive: user.lastLogin && Date.now() - user.lastLogin < 30 * 24 * 60 * 60 * 1000
308
}));
309
});
310
311
// Cache API calls
312
const fetchUserById = cache(async (userId: string) => {
313
console.log(`Fetching user ${userId}...`);
314
const response = await fetch(`/api/users/${userId}`);
315
return response.json();
316
});
317
318
function UserList({ users }: { users: any[] }) {
319
// Will use cached result if users array hasn't changed
320
const processedUsers = processUserData(users);
321
322
return (
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
```
333
334
## Advanced Performance Patterns
335
336
### Virtualization Pattern
337
338
```typescript
339
import React, { useMemo, useState, useRef, useLayoutEffect } from "react";
340
341
interface VirtualizedListProps {
342
items: any[];
343
itemHeight: number;
344
containerHeight: number;
345
renderItem: (item: any, index: number) => React.ReactNode;
346
}
347
348
function VirtualizedList({ items, itemHeight, containerHeight, renderItem }: VirtualizedListProps) {
349
const [scrollTop, setScrollTop] = useState(0);
350
const containerRef = useRef<HTMLDivElement>(null);
351
352
const visibleItems = useMemo(() => {
353
const startIndex = Math.floor(scrollTop / itemHeight);
354
const endIndex = Math.min(
355
startIndex + Math.ceil(containerHeight / itemHeight) + 1,
356
items.length
357
);
358
359
return items.slice(startIndex, endIndex).map((item, index) => ({
360
item,
361
index: startIndex + index
362
}));
363
}, [items, itemHeight, containerHeight, scrollTop]);
364
365
const totalHeight = items.length * itemHeight;
366
const offsetY = Math.floor(scrollTop / itemHeight) * itemHeight;
367
368
return (
369
<div
370
ref={containerRef}
371
style={{ height: containerHeight, overflow: "auto" }}
372
onScroll={(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
```
387
388
### Debounced Input Pattern
389
390
```typescript
391
import React, { useState, useMemo, useCallback } from "react";
392
393
function useDebounce<T>(value: T, delay: number): T {
394
const [debouncedValue, setDebouncedValue] = useState(value);
395
396
React.useEffect(() => {
397
const handler = setTimeout(() => {
398
setDebouncedValue(value);
399
}, delay);
400
401
return () => {
402
clearTimeout(handler);
403
};
404
}, [value, delay]);
405
406
return debouncedValue;
407
}
408
409
function SearchableList({ items }: { items: string[] }) {
410
const [searchTerm, setSearchTerm] = useState("");
411
const debouncedSearchTerm = useDebounce(searchTerm, 300);
412
413
// Expensive filtering only happens after debounce delay
414
const filteredItems = useMemo(() => {
415
console.log("Filtering items...");
416
return items.filter(item =>
417
item.toLowerCase().includes(debouncedSearchTerm.toLowerCase())
418
);
419
}, [items, debouncedSearchTerm]);
420
421
return (
422
<div>
423
<input
424
type="text"
425
value={searchTerm}
426
onChange={(e) => setSearchTerm(e.target.value)}
427
placeholder="Search items..."
428
/>
429
<ul>
430
{filteredItems.map((item, index) => (
431
<li key={index}>{item}</li>
432
))}
433
</ul>
434
</div>
435
);
436
}
437
```
438
439
## Types
440
441
### Performance-Related Types
442
443
```typescript { .api }
444
interface NamedExoticComponent<P = {}> extends ExoticComponent<P> {
445
displayName?: string;
446
}
447
448
interface ExoticComponent<P = {}> {
449
(props: P): ReactElement | null;
450
readonly $$typeof: symbol;
451
}
452
453
interface LazyExoticComponent<T extends ComponentType<any>> extends ExoticComponent<ComponentPropsWithRef<T>> {
454
readonly _result: T;
455
}
456
457
type ComponentPropsWithRef<T extends ElementType> = T extends ComponentClass<infer P>
458
? PropsWithoutRef<P> & RefAttributes<InstanceType<T>>
459
: PropsWithRef<ComponentProps<T>>;
460
461
type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =
462
T extends JSXElementConstructor<infer P>
463
? P
464
: T extends keyof JSX.IntrinsicElements
465
? JSX.IntrinsicElements[T]
466
: {};
467
```