react-suspense

Full Reference: See advanced.md for Streaming SSR, SuspenseList, Custom Suspense-Enabled Hooks, Image Loading, Route-Based Code Splitting, and Testing patterns.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-suspense" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-react-suspense

React Suspense

Full Reference: See advanced.md for Streaming SSR, SuspenseList, Custom Suspense-Enabled Hooks, Image Loading, Route-Based Code Splitting, and Testing patterns.

When NOT to Use This Skill

  • Using React 17 or earlier (limited support)

  • Working with class components

  • Building non-React applications

  • All data is static (no async operations)

Core Concept

Suspense lets you declaratively specify loading states while waiting for async operations:

import { Suspense } from 'react';

function App() { return ( <Suspense fallback={<LoadingSpinner />}> <AsyncComponent /> </Suspense> ); }

Code Splitting with React.lazy

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./Dashboard')); const Settings = lazy(() => import('./Settings'));

function App() { const [view, setView] = useState('dashboard');

return ( <div> <nav> <button onClick={() => setView('dashboard')}>Dashboard</button> <button onClick={() => setView('settings')}>Settings</button> </nav>

  &#x3C;Suspense fallback={&#x3C;PageSkeleton />}>
    {view === 'dashboard' &#x26;&#x26; &#x3C;Dashboard />}
    {view === 'settings' &#x26;&#x26; &#x3C;Settings />}
  &#x3C;/Suspense>
&#x3C;/div>

); }

Preloading Components

const Dashboard = lazy(() => import('./Dashboard'));

const preloadDashboard = () => import('./Dashboard');

function NavLink() { return ( <Link to="/dashboard" onMouseEnter={preloadDashboard} onFocus={preloadDashboard} > Dashboard </Link> ); }

Data Fetching with Suspense

Using TanStack Query

import { useSuspenseQuery } from '@tanstack/react-query';

function UserProfile({ userId }: { userId: string }) { const { data: user } = useSuspenseQuery({ queryKey: ['user', userId], queryFn: () => fetchUser(userId), });

return <h1>{user.name}</h1>; }

function UserPage({ userId }: { userId: string }) { return ( <Suspense fallback={<UserSkeleton />}> <UserProfile userId={userId} /> </Suspense> ); }

Using React 19 use() Hook

import { use, Suspense } from 'react';

function UserProfile({ userPromise }: { userPromise: Promise<User> }) { const user = use(userPromise); return <h1>{user.name}</h1>; }

function UserPage({ userId }: { userId: string }) { const [userPromise] = useState(() => fetchUser(userId));

return ( <Suspense fallback={<UserSkeleton />}> <UserProfile userPromise={userPromise} /> </Suspense> ); }

Nested Suspense Boundaries

function Dashboard() { return ( <div className="dashboard"> <Suspense fallback={<HeaderSkeleton />}> <Header /> </Suspense>

  &#x3C;main>
    &#x3C;Suspense fallback={&#x3C;StatsSkeleton />}>
      &#x3C;Stats />
    &#x3C;/Suspense>

    &#x3C;Suspense fallback={&#x3C;ChartsSkeleton />}>
      &#x3C;Charts />
    &#x3C;/Suspense>

    &#x3C;Suspense fallback={&#x3C;TableSkeleton />}>
      &#x3C;DataTable />
    &#x3C;/Suspense>
  &#x3C;/main>
&#x3C;/div>

); }

Error Boundaries with Suspense

import { ErrorBoundary, FallbackProps } from 'react-error-boundary';

function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { return ( <div> <h2>Something went wrong</h2> <pre>{error.message}</pre> <button onClick={resetErrorBoundary}>Try again</button> </div> ); }

// Reusable wrapper function AsyncBoundary({ children, fallback, errorFallback, }: { children: React.ReactNode; fallback: React.ReactNode; errorFallback: React.ComponentType<FallbackProps>; }) { return ( <ErrorBoundary FallbackComponent={errorFallback}> <Suspense fallback={fallback}>{children}</Suspense> </ErrorBoundary> ); }

// Usage <AsyncBoundary fallback={<LoadingSpinner />} errorFallback={ErrorFallback}

<AsyncComponent /> </AsyncBoundary>

Progressive Loading

function ArticlePage({ articleId }: { articleId: string }) { return ( <article> {/* Critical content loads first */} <Suspense fallback={<TitleSkeleton />}> <ArticleTitle articleId={articleId} /> </Suspense>

  {/* Content loads next */}
  &#x3C;Suspense fallback={&#x3C;ContentSkeleton />}>
    &#x3C;ArticleContent articleId={articleId} />
  &#x3C;/Suspense>

  {/* Less critical - loads last */}
  &#x3C;Suspense fallback={&#x3C;CommentsSkeleton />}>
    &#x3C;Comments articleId={articleId} />
  &#x3C;/Suspense>
&#x3C;/article>

); }

With Transition for Updates

import { useState, useTransition, Suspense } from 'react';

function TabContainer() { const [tab, setTab] = useState('about'); const [isPending, startTransition] = useTransition();

function selectTab(nextTab: string) { startTransition(() => setTab(nextTab)); }

return ( <> <TabButtons selectedTab={tab} onSelect={selectTab} />

  &#x3C;div className={isPending ? 'opacity-50' : ''}>
    &#x3C;Suspense fallback={&#x3C;TabSkeleton />}>
      {tab === 'about' &#x26;&#x26; &#x3C;About />}
      {tab === 'posts' &#x26;&#x26; &#x3C;Posts />}
    &#x3C;/Suspense>
  &#x3C;/div>
&#x3C;/>

); }

Skeleton Loading Patterns

function UserCardSkeleton() { return ( <div className="user-card"> <div className="skeleton skeleton-avatar" /> <div className="skeleton skeleton-text" style={{ width: '60%' }} /> <div className="skeleton skeleton-text" style={{ width: '40%' }} /> </div> ); }

// CSS const skeletonStyles = ` .skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: skeleton-loading 1.5s infinite; border-radius: 4px; }

@keyframes skeleton-loading { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } `;

Anti-Patterns

Anti-Pattern Why It's Bad Correct Approach

Creating promises in render New promise every render Create outside component

Too many Suspense boundaries Over-fragmented loading Group related content

Too few boundaries Entire app suspends Add boundaries per section

No ErrorBoundary Errors crash app Wrap Suspense in ErrorBoundary

Generic loading spinners Poor UX Use skeleton loaders

Quick Troubleshooting

Issue Solution

Infinite suspending Move promise creation outside component

Flash of loading state Add delay before showing fallback

Waterfall loading Fetch data in parallel

Lost scroll position Use skeletons with same dimensions

Error not caught Add ErrorBoundary wrapper

Best Practices

  • ✅ Place Suspense at meaningful UI boundaries

  • ✅ Use skeleton loaders matching content dimensions

  • ✅ Combine with ErrorBoundary for complete error handling

  • ✅ Use transitions for non-urgent updates

  • ✅ Preload components on user intent (hover, focus)

  • ❌ Don't create Promises inside components

  • ❌ Don't use too many granular boundaries

Reference Documentation

  • React Suspense

  • Lazy Loading

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review