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

suspense.md docs/

1
# Suspense and Error Boundaries
2
3
Components and utilities for handling async loading states and errors in React applications. These components help manage loading states and error recovery in a declarative way.
4
5
## Capabilities
6
7
### Suspense
8
9
Component for handling loading states of lazy-loaded components and async operations.
10
11
```typescript { .api }
12
/**
13
* Handles loading states for async components and operations
14
*/
15
interface SuspenseProps {
16
/**
17
* Components that may suspend during rendering
18
*/
19
children?: React.ReactNode;
20
/**
21
* Fallback UI to show while children are loading
22
*/
23
fallback: React.ReactNode;
24
}
25
26
const Suspense: React.ExoticComponent<SuspenseProps>;
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import React, { Suspense, lazy } from "react";
33
34
// Lazy-loaded components
35
const Dashboard = lazy(() => import("./Dashboard"));
36
const UserProfile = lazy(() => import("./UserProfile"));
37
const Settings = lazy(() => import("./Settings"));
38
39
function App() {
40
return (
41
<div>
42
<header>My App</header>
43
44
<main>
45
<Suspense fallback={<div>Loading dashboard...</div>}>
46
<Dashboard />
47
</Suspense>
48
49
<Suspense fallback={<div>Loading profile...</div>}>
50
<UserProfile />
51
</Suspense>
52
</main>
53
</div>
54
);
55
}
56
57
// Nested Suspense boundaries
58
function NestedSuspenseExample() {
59
return (
60
<Suspense fallback={<div>Loading app...</div>}>
61
<Header />
62
63
<Suspense fallback={<div>Loading main content...</div>}>
64
<MainContent />
65
66
<Suspense fallback={<div>Loading sidebar...</div>}>
67
<Sidebar />
68
</Suspense>
69
</Suspense>
70
</Suspense>
71
);
72
}
73
```
74
75
### Error Boundaries
76
77
Class components for catching JavaScript errors in component trees and displaying fallback UI.
78
79
```typescript { .api }
80
/**
81
* Catches JavaScript errors anywhere in child component tree
82
*/
83
interface ErrorBoundaryState {
84
hasError: boolean;
85
error?: Error;
86
}
87
88
interface ErrorBoundaryProps {
89
children: React.ReactNode;
90
fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;
91
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
92
}
93
94
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
95
constructor(props: ErrorBoundaryProps);
96
97
/**
98
* Updates state to show error UI
99
*/
100
static getDerivedStateFromError(error: Error): ErrorBoundaryState;
101
102
/**
103
* Logs error information
104
*/
105
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;
106
107
render(): React.ReactNode;
108
}
109
```
110
111
**Usage Examples:**
112
113
```typescript
114
import React, { Component, ErrorInfo, ReactNode } from "react";
115
116
interface Props {
117
children: ReactNode;
118
fallback?: ComponentType<{ error: Error; resetError: () => void }>;
119
onError?: (error: Error, errorInfo: ErrorInfo) => void;
120
}
121
122
interface State {
123
hasError: boolean;
124
error?: Error;
125
}
126
127
class ErrorBoundary extends Component<Props, State> {
128
constructor(props: Props) {
129
super(props);
130
this.state = { hasError: false };
131
}
132
133
static getDerivedStateFromError(error: Error): State {
134
return { hasError: true, error };
135
}
136
137
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
138
console.error("Error caught by boundary:", error, errorInfo);
139
this.props.onError?.(error, errorInfo);
140
}
141
142
resetError = () => {
143
this.setState({ hasError: false, error: undefined });
144
};
145
146
render() {
147
if (this.state.hasError) {
148
if (this.props.fallback) {
149
const FallbackComponent = this.props.fallback;
150
return (
151
<FallbackComponent
152
error={this.state.error!}
153
resetError={this.resetError}
154
/>
155
);
156
}
157
158
return (
159
<div>
160
<h2>Something went wrong</h2>
161
<details>
162
{this.state.error?.message}
163
</details>
164
<button onClick={this.resetError}>Try again</button>
165
</div>
166
);
167
}
168
169
return this.props.children;
170
}
171
}
172
173
// Usage
174
function App() {
175
return (
176
<ErrorBoundary
177
fallback={({ error, resetError }) => (
178
<div>
179
<h1>Application Error</h1>
180
<p>{error.message}</p>
181
<button onClick={resetError}>Reset</button>
182
</div>
183
)}
184
onError={(error, errorInfo) => {
185
// Log to error reporting service
186
console.error("App error:", error, errorInfo);
187
}}
188
>
189
<Header />
190
<MainContent />
191
<Footer />
192
</ErrorBoundary>
193
);
194
}
195
```
196
197
### use Hook
198
199
Hook for reading promises and context (Suspense integration).
200
201
```typescript { .api }
202
/**
203
* Reads the result of a Promise or Context
204
* @param resource - Promise or Context to read from
205
* @returns Resolved value or context value
206
*/
207
function use<T>(resource: Promise<T>): T;
208
function use<T>(resource: React.Context<T>): T;
209
```
210
211
**Usage Examples:**
212
213
```typescript
214
import React, { use, Suspense } from "react";
215
216
// Using with Promises
217
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
218
const user = use(userPromise);
219
220
return (
221
<div>
222
<h1>{user.name}</h1>
223
<p>{user.email}</p>
224
</div>
225
);
226
}
227
228
function App() {
229
const userPromise = fetchUser("123");
230
231
return (
232
<Suspense fallback={<div>Loading user...</div>}>
233
<UserProfile userPromise={userPromise} />
234
</Suspense>
235
);
236
}
237
238
// Using with Context
239
const ThemeContext = React.createContext("light");
240
241
function ThemedButton() {
242
const theme = use(ThemeContext);
243
244
return (
245
<button className={`button-${theme}`}>
246
Themed Button
247
</button>
248
);
249
}
250
251
async function fetchUser(id: string): Promise<User> {
252
const response = await fetch(`/api/users/${id}`);
253
return response.json();
254
}
255
256
interface User {
257
name: string;
258
email: string;
259
}
260
```
261
262
## Advanced Patterns
263
264
### Combined Suspense and Error Boundaries
265
266
```typescript
267
import React, { Suspense, lazy, Component, ErrorInfo, ReactNode } from "react";
268
269
class SuspenseErrorBoundary extends Component<
270
{ children: ReactNode; fallback: ReactNode },
271
{ hasError: boolean }
272
> {
273
constructor(props: any) {
274
super(props);
275
this.state = { hasError: false };
276
}
277
278
static getDerivedStateFromError() {
279
return { hasError: true };
280
}
281
282
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
283
console.error("Suspense error:", error, errorInfo);
284
}
285
286
render() {
287
if (this.state.hasError) {
288
return (
289
<div>
290
<h2>Failed to load component</h2>
291
<button onClick={() => this.setState({ hasError: false })}>
292
Retry
293
</button>
294
</div>
295
);
296
}
297
298
return (
299
<Suspense fallback={this.props.fallback}>
300
{this.props.children}
301
</Suspense>
302
);
303
}
304
}
305
306
// Usage
307
const LazyComponent = lazy(() => import("./LazyComponent"));
308
309
function App() {
310
return (
311
<SuspenseErrorBoundary fallback={<div>Loading...</div>}>
312
<LazyComponent />
313
</SuspenseErrorBoundary>
314
);
315
}
316
```
317
318
### Resource-Based Suspense
319
320
```typescript
321
import React, { use, Suspense, createContext, useContext } from "react";
322
323
// Resource pattern for data fetching
324
function createResource<T>(promise: Promise<T>) {
325
let status = "pending";
326
let result: T;
327
let error: Error;
328
329
const suspender = promise.then(
330
(res) => {
331
status = "success";
332
result = res;
333
},
334
(err) => {
335
status = "error";
336
error = err;
337
}
338
);
339
340
return {
341
read() {
342
if (status === "pending") {
343
throw suspender;
344
} else if (status === "error") {
345
throw error;
346
} else {
347
return result;
348
}
349
}
350
};
351
}
352
353
// Data fetching with Suspense
354
function UserList() {
355
const usersResource = createResource(fetchUsers());
356
357
return (
358
<Suspense fallback={<div>Loading users...</div>}>
359
<UserListContent usersResource={usersResource} />
360
</Suspense>
361
);
362
}
363
364
function UserListContent({ usersResource }: { usersResource: any }) {
365
const users = usersResource.read();
366
367
return (
368
<ul>
369
{users.map((user: any) => (
370
<li key={user.id}>{user.name}</li>
371
))}
372
</ul>
373
);
374
}
375
376
async function fetchUsers() {
377
const response = await fetch("/api/users");
378
return response.json();
379
}
380
```
381
382
### Progressive Loading with Multiple Suspense Boundaries
383
384
```typescript
385
import React, { Suspense, lazy } from "react";
386
387
const Header = lazy(() => import("./Header"));
388
const Sidebar = lazy(() => import("./Sidebar"));
389
const MainContent = lazy(() => import("./MainContent"));
390
const Footer = lazy(() => import("./Footer"));
391
392
function ProgressiveApp() {
393
return (
394
<div className="app">
395
{/* Header loads first */}
396
<Suspense fallback={<div className="header-skeleton">Loading header...</div>}>
397
<Header />
398
</Suspense>
399
400
<div className="app-body">
401
{/* Sidebar and main content load independently */}
402
<Suspense fallback={<div className="sidebar-skeleton">Loading sidebar...</div>}>
403
<Sidebar />
404
</Suspense>
405
406
<Suspense fallback={<div className="main-skeleton">Loading content...</div>}>
407
<MainContent />
408
</Suspense>
409
</div>
410
411
{/* Footer loads last */}
412
<Suspense fallback={<div className="footer-skeleton">Loading footer...</div>}>
413
<Footer />
414
</Suspense>
415
</div>
416
);
417
}
418
```
419
420
### Suspense with Data Dependencies
421
422
```typescript
423
import React, { use, Suspense, useMemo } from "react";
424
425
function UserDashboard({ userId }: { userId: string }) {
426
const userPromise = useMemo(() => fetchUser(userId), [userId]);
427
const postsPromise = useMemo(() => fetchUserPosts(userId), [userId]);
428
429
return (
430
<div>
431
<Suspense fallback={<div>Loading user...</div>}>
432
<UserHeader userPromise={userPromise} />
433
</Suspense>
434
435
<Suspense fallback={<div>Loading posts...</div>}>
436
<UserPosts postsPromise={postsPromise} />
437
</Suspense>
438
</div>
439
);
440
}
441
442
function UserHeader({ userPromise }: { userPromise: Promise<any> }) {
443
const user = use(userPromise);
444
return <h1>Welcome, {user.name}!</h1>;
445
}
446
447
function UserPosts({ postsPromise }: { postsPromise: Promise<any[]> }) {
448
const posts = use(postsPromise);
449
return (
450
<div>
451
{posts.map(post => (
452
<article key={post.id}>
453
<h3>{post.title}</h3>
454
<p>{post.content}</p>
455
</article>
456
))}
457
</div>
458
);
459
}
460
461
async function fetchUser(id: string) {
462
const response = await fetch(`/api/users/${id}`);
463
return response.json();
464
}
465
466
async function fetchUserPosts(userId: string) {
467
const response = await fetch(`/api/users/${userId}/posts`);
468
return response.json();
469
}
470
```
471
472
## Experimental/Unstable Features
473
474
### unstable_SuspenseList
475
476
Coordinates the reveal order of multiple Suspense boundaries.
477
478
```typescript { .api }
479
/**
480
* Controls the order in which Suspense boundaries are revealed
481
*/
482
interface SuspenseListProps {
483
children: React.ReactNode;
484
revealOrder?: "forwards" | "backwards" | "together";
485
tail?: "collapsed" | "hidden";
486
}
487
488
const SuspenseList: React.ExoticComponent<SuspenseListProps>;
489
```
490
491
**Usage Examples:**
492
493
```typescript
494
import React, { Suspense } from "react";
495
import { unstable_SuspenseList as SuspenseList } from "react";
496
497
function OrderedList() {
498
return (
499
<SuspenseList revealOrder="forwards">
500
<Suspense fallback={<div>Loading first item...</div>}>
501
<SlowComponent delay={1000} />
502
</Suspense>
503
504
<Suspense fallback={<div>Loading second item...</div>}>
505
<SlowComponent delay={500} />
506
</Suspense>
507
508
<Suspense fallback={<div>Loading third item...</div>}>
509
<SlowComponent delay={2000} />
510
</Suspense>
511
</SuspenseList>
512
);
513
}
514
515
function SlowComponent({ delay }: { delay: number }) {
516
// Component that takes time to load
517
return <div>Loaded after {delay}ms</div>;
518
}
519
```
520
521
### Other Experimental Components
522
523
Additional experimental components for advanced React patterns.
524
525
```typescript { .api }
526
/**
527
* Legacy hidden component for compatibility
528
*/
529
const unstable_LegacyHidden: React.ExoticComponent<{ children?: React.ReactNode }>;
530
531
/**
532
* Activity component for scheduling priorities
533
*/
534
const unstable_Activity: React.ExoticComponent<{ children?: React.ReactNode }>;
535
536
/**
537
* Scope component for DOM measurements and queries
538
*/
539
const unstable_Scope: React.ExoticComponent<{ children?: React.ReactNode }>;
540
541
/**
542
* Tracing marker for performance profiling
543
*/
544
const unstable_TracingMarker: React.ExoticComponent<{ children?: React.ReactNode }>;
545
546
/**
547
* View transition component for smooth navigation
548
*/
549
const unstable_ViewTransition: React.ExoticComponent<{ children?: React.ReactNode }>;
550
```
551
552
### Server-Side Functions
553
554
```typescript { .api }
555
/**
556
* Postpones component rendering (Server Components)
557
* @param reason - Reason for postponing
558
*/
559
function unstable_postpone(reason: string): void;
560
```
561
562
## Types
563
564
### Suspense-Related Types
565
566
```typescript { .api }
567
interface SuspenseProps {
568
children?: ReactNode;
569
fallback: ReactNode;
570
}
571
572
interface ErrorInfo {
573
componentStack: string;
574
}
575
576
type SuspenseResource<T> = {
577
read(): T;
578
};
579
580
interface LazyExoticComponent<T extends ComponentType<any>> extends ExoticComponent<ComponentPropsWithRef<T>> {
581
readonly _result: T;
582
readonly _payload: {
583
_status: "pending" | "resolved" | "rejected";
584
_result: any;
585
};
586
}
587
588
type Usable<T> = Thenable<T> | Context<T>;
589
590
interface Thenable<T> {
591
then(onFulfill: (value: T) => any, onReject: (reason: any) => any): any;
592
}
593
```