- 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
suspense.md docs/
1# Suspense and Error Boundaries23Components 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.45## Capabilities67### Suspense89Component for handling loading states of lazy-loaded components and async operations.1011```typescript { .api }12/**13* Handles loading states for async components and operations14*/15interface SuspenseProps {16/**17* Components that may suspend during rendering18*/19children?: React.ReactNode;20/**21* Fallback UI to show while children are loading22*/23fallback: React.ReactNode;24}2526const Suspense: React.ExoticComponent<SuspenseProps>;27```2829**Usage Examples:**3031```typescript32import React, { Suspense, lazy } from "react";3334// Lazy-loaded components35const Dashboard = lazy(() => import("./Dashboard"));36const UserProfile = lazy(() => import("./UserProfile"));37const Settings = lazy(() => import("./Settings"));3839function App() {40return (41<div>42<header>My App</header>4344<main>45<Suspense fallback={<div>Loading dashboard...</div>}>46<Dashboard />47</Suspense>4849<Suspense fallback={<div>Loading profile...</div>}>50<UserProfile />51</Suspense>52</main>53</div>54);55}5657// Nested Suspense boundaries58function NestedSuspenseExample() {59return (60<Suspense fallback={<div>Loading app...</div>}>61<Header />6263<Suspense fallback={<div>Loading main content...</div>}>64<MainContent />6566<Suspense fallback={<div>Loading sidebar...</div>}>67<Sidebar />68</Suspense>69</Suspense>70</Suspense>71);72}73```7475### Error Boundaries7677Class components for catching JavaScript errors in component trees and displaying fallback UI.7879```typescript { .api }80/**81* Catches JavaScript errors anywhere in child component tree82*/83interface ErrorBoundaryState {84hasError: boolean;85error?: Error;86}8788interface ErrorBoundaryProps {89children: React.ReactNode;90fallback?: React.ComponentType<{ error: Error; resetError: () => void }>;91onError?: (error: Error, errorInfo: React.ErrorInfo) => void;92}9394class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {95constructor(props: ErrorBoundaryProps);9697/**98* Updates state to show error UI99*/100static getDerivedStateFromError(error: Error): ErrorBoundaryState;101102/**103* Logs error information104*/105componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void;106107render(): React.ReactNode;108}109```110111**Usage Examples:**112113```typescript114import React, { Component, ErrorInfo, ReactNode } from "react";115116interface Props {117children: ReactNode;118fallback?: ComponentType<{ error: Error; resetError: () => void }>;119onError?: (error: Error, errorInfo: ErrorInfo) => void;120}121122interface State {123hasError: boolean;124error?: Error;125}126127class ErrorBoundary extends Component<Props, State> {128constructor(props: Props) {129super(props);130this.state = { hasError: false };131}132133static getDerivedStateFromError(error: Error): State {134return { hasError: true, error };135}136137componentDidCatch(error: Error, errorInfo: ErrorInfo) {138console.error("Error caught by boundary:", error, errorInfo);139this.props.onError?.(error, errorInfo);140}141142resetError = () => {143this.setState({ hasError: false, error: undefined });144};145146render() {147if (this.state.hasError) {148if (this.props.fallback) {149const FallbackComponent = this.props.fallback;150return (151<FallbackComponent152error={this.state.error!}153resetError={this.resetError}154/>155);156}157158return (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}168169return this.props.children;170}171}172173// Usage174function App() {175return (176<ErrorBoundary177fallback={({ error, resetError }) => (178<div>179<h1>Application Error</h1>180<p>{error.message}</p>181<button onClick={resetError}>Reset</button>182</div>183)}184onError={(error, errorInfo) => {185// Log to error reporting service186console.error("App error:", error, errorInfo);187}}188>189<Header />190<MainContent />191<Footer />192</ErrorBoundary>193);194}195```196197### use Hook198199Hook for reading promises and context (Suspense integration).200201```typescript { .api }202/**203* Reads the result of a Promise or Context204* @param resource - Promise or Context to read from205* @returns Resolved value or context value206*/207function use<T>(resource: Promise<T>): T;208function use<T>(resource: React.Context<T>): T;209```210211**Usage Examples:**212213```typescript214import React, { use, Suspense } from "react";215216// Using with Promises217function UserProfile({ userPromise }: { userPromise: Promise<User> }) {218const user = use(userPromise);219220return (221<div>222<h1>{user.name}</h1>223<p>{user.email}</p>224</div>225);226}227228function App() {229const userPromise = fetchUser("123");230231return (232<Suspense fallback={<div>Loading user...</div>}>233<UserProfile userPromise={userPromise} />234</Suspense>235);236}237238// Using with Context239const ThemeContext = React.createContext("light");240241function ThemedButton() {242const theme = use(ThemeContext);243244return (245<button className={`button-${theme}`}>246Themed Button247</button>248);249}250251async function fetchUser(id: string): Promise<User> {252const response = await fetch(`/api/users/${id}`);253return response.json();254}255256interface User {257name: string;258email: string;259}260```261262## Advanced Patterns263264### Combined Suspense and Error Boundaries265266```typescript267import React, { Suspense, lazy, Component, ErrorInfo, ReactNode } from "react";268269class SuspenseErrorBoundary extends Component<270{ children: ReactNode; fallback: ReactNode },271{ hasError: boolean }272> {273constructor(props: any) {274super(props);275this.state = { hasError: false };276}277278static getDerivedStateFromError() {279return { hasError: true };280}281282componentDidCatch(error: Error, errorInfo: ErrorInfo) {283console.error("Suspense error:", error, errorInfo);284}285286render() {287if (this.state.hasError) {288return (289<div>290<h2>Failed to load component</h2>291<button onClick={() => this.setState({ hasError: false })}>292Retry293</button>294</div>295);296}297298return (299<Suspense fallback={this.props.fallback}>300{this.props.children}301</Suspense>302);303}304}305306// Usage307const LazyComponent = lazy(() => import("./LazyComponent"));308309function App() {310return (311<SuspenseErrorBoundary fallback={<div>Loading...</div>}>312<LazyComponent />313</SuspenseErrorBoundary>314);315}316```317318### Resource-Based Suspense319320```typescript321import React, { use, Suspense, createContext, useContext } from "react";322323// Resource pattern for data fetching324function createResource<T>(promise: Promise<T>) {325let status = "pending";326let result: T;327let error: Error;328329const suspender = promise.then(330(res) => {331status = "success";332result = res;333},334(err) => {335status = "error";336error = err;337}338);339340return {341read() {342if (status === "pending") {343throw suspender;344} else if (status === "error") {345throw error;346} else {347return result;348}349}350};351}352353// Data fetching with Suspense354function UserList() {355const usersResource = createResource(fetchUsers());356357return (358<Suspense fallback={<div>Loading users...</div>}>359<UserListContent usersResource={usersResource} />360</Suspense>361);362}363364function UserListContent({ usersResource }: { usersResource: any }) {365const users = usersResource.read();366367return (368<ul>369{users.map((user: any) => (370<li key={user.id}>{user.name}</li>371))}372</ul>373);374}375376async function fetchUsers() {377const response = await fetch("/api/users");378return response.json();379}380```381382### Progressive Loading with Multiple Suspense Boundaries383384```typescript385import React, { Suspense, lazy } from "react";386387const Header = lazy(() => import("./Header"));388const Sidebar = lazy(() => import("./Sidebar"));389const MainContent = lazy(() => import("./MainContent"));390const Footer = lazy(() => import("./Footer"));391392function ProgressiveApp() {393return (394<div className="app">395{/* Header loads first */}396<Suspense fallback={<div className="header-skeleton">Loading header...</div>}>397<Header />398</Suspense>399400<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>405406<Suspense fallback={<div className="main-skeleton">Loading content...</div>}>407<MainContent />408</Suspense>409</div>410411{/* Footer loads last */}412<Suspense fallback={<div className="footer-skeleton">Loading footer...</div>}>413<Footer />414</Suspense>415</div>416);417}418```419420### Suspense with Data Dependencies421422```typescript423import React, { use, Suspense, useMemo } from "react";424425function UserDashboard({ userId }: { userId: string }) {426const userPromise = useMemo(() => fetchUser(userId), [userId]);427const postsPromise = useMemo(() => fetchUserPosts(userId), [userId]);428429return (430<div>431<Suspense fallback={<div>Loading user...</div>}>432<UserHeader userPromise={userPromise} />433</Suspense>434435<Suspense fallback={<div>Loading posts...</div>}>436<UserPosts postsPromise={postsPromise} />437</Suspense>438</div>439);440}441442function UserHeader({ userPromise }: { userPromise: Promise<any> }) {443const user = use(userPromise);444return <h1>Welcome, {user.name}!</h1>;445}446447function UserPosts({ postsPromise }: { postsPromise: Promise<any[]> }) {448const posts = use(postsPromise);449return (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}460461async function fetchUser(id: string) {462const response = await fetch(`/api/users/${id}`);463return response.json();464}465466async function fetchUserPosts(userId: string) {467const response = await fetch(`/api/users/${userId}/posts`);468return response.json();469}470```471472## Experimental/Unstable Features473474### unstable_SuspenseList475476Coordinates the reveal order of multiple Suspense boundaries.477478```typescript { .api }479/**480* Controls the order in which Suspense boundaries are revealed481*/482interface SuspenseListProps {483children: React.ReactNode;484revealOrder?: "forwards" | "backwards" | "together";485tail?: "collapsed" | "hidden";486}487488const SuspenseList: React.ExoticComponent<SuspenseListProps>;489```490491**Usage Examples:**492493```typescript494import React, { Suspense } from "react";495import { unstable_SuspenseList as SuspenseList } from "react";496497function OrderedList() {498return (499<SuspenseList revealOrder="forwards">500<Suspense fallback={<div>Loading first item...</div>}>501<SlowComponent delay={1000} />502</Suspense>503504<Suspense fallback={<div>Loading second item...</div>}>505<SlowComponent delay={500} />506</Suspense>507508<Suspense fallback={<div>Loading third item...</div>}>509<SlowComponent delay={2000} />510</Suspense>511</SuspenseList>512);513}514515function SlowComponent({ delay }: { delay: number }) {516// Component that takes time to load517return <div>Loaded after {delay}ms</div>;518}519```520521### Other Experimental Components522523Additional experimental components for advanced React patterns.524525```typescript { .api }526/**527* Legacy hidden component for compatibility528*/529const unstable_LegacyHidden: React.ExoticComponent<{ children?: React.ReactNode }>;530531/**532* Activity component for scheduling priorities533*/534const unstable_Activity: React.ExoticComponent<{ children?: React.ReactNode }>;535536/**537* Scope component for DOM measurements and queries538*/539const unstable_Scope: React.ExoticComponent<{ children?: React.ReactNode }>;540541/**542* Tracing marker for performance profiling543*/544const unstable_TracingMarker: React.ExoticComponent<{ children?: React.ReactNode }>;545546/**547* View transition component for smooth navigation548*/549const unstable_ViewTransition: React.ExoticComponent<{ children?: React.ReactNode }>;550```551552### Server-Side Functions553554```typescript { .api }555/**556* Postpones component rendering (Server Components)557* @param reason - Reason for postponing558*/559function unstable_postpone(reason: string): void;560```561562## Types563564### Suspense-Related Types565566```typescript { .api }567interface SuspenseProps {568children?: ReactNode;569fallback: ReactNode;570}571572interface ErrorInfo {573componentStack: string;574}575576type SuspenseResource<T> = {577read(): T;578};579580interface LazyExoticComponent<T extends ComponentType<any>> extends ExoticComponent<ComponentPropsWithRef<T>> {581readonly _result: T;582readonly _payload: {583_status: "pending" | "resolved" | "rejected";584_result: any;585};586}587588type Usable<T> = Thenable<T> | Context<T>;589590interface Thenable<T> {591then(onFulfill: (value: T) => any, onReject: (reason: any) => any): any;592}593```