All skills
github avatar

/react19-concurrent-patterns

@90921cc official
by githubgithub/awesome-copilot40k stars
5,040

Preserve React 18 concurrent patterns and adopt React 19 APIs (useTransition, useDeferredValue, Suspense, use(), useOptimistic, Actions) during migration.

Use this Skill: https://skilld.dev/gh/github/awesome-copilot/react19-concurrent-patterns

This session only. Nothing lands on disk.

referencesreact19-suspense.md

≈1.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

React 19 Suspense for Data Fetching Pattern Reference

React 19's new Suspense integration for data fetching is a preview feature that allows components to suspend (pause rendering) until data is available, without useEffect + state.

Important: This is a preview it requires specific setup and is not yet stable for production, but you should know the pattern for React 19 migration planning.


What Changed in React 19?

React 18 Suspense only supported code splitting (lazy components). React 19 extends it to data fetching if certain conditions are met:

  • Lib usage the data fetching library must implement Suspense (e.g., React Query 5+, SWR, Remix loaders)
  • Or your own promise tracking wrap promises in a way React can track their suspension
  • No more "no hook after suspense" you can use Suspense directly in components with use()

React 18 Suspense (Code Splitting Only)

// React 18  Suspense for lazy imports only:
const LazyComponent = React.lazy(() => import('./Component'));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <LazyComponent />
    </Suspense>
  );
}

Trying to suspend for data in React 18 required hacks or libraries:

// React 18 hack  not recommended:
const dataPromise = fetchData();
const resource = {
  read: () => {
    throw dataPromise; // Throw to suspend
  }
};

function Component() {
  const data = resource.read(); // Throws promise → Suspense catches it
  return <div>{data}</div>;
}

React 19 Suspense for Data Fetching (Preview)

React 19 provides first-class support for Suspense with promises via the use() hook:

// React 19  Suspense for data fetching:
function UserProfile({ userId }) {
  const user = use(fetchUser(userId)); // Suspends if promise pending
  return <div>{user.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile userId={123} />
    </Suspense>
  );
}

Key differences from React 18:

  • use() unwraps the promise, component suspends automatically
  • No need for useEffect + state trick
  • Cleaner code, less boilerplate

Pattern 1: Simple Promise Suspense

// Raw promise (not recommended in production):
function DataComponent() {
  const data = use(fetch('/api/data').then(r => r.json()));
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <DataComponent />
    </Suspense>
  );
}

Problem: Promise is recreated every render. Solution: wrap in useMemo.


Pattern 2: Memoized Promise (Better)

function DataComponent({ id }) {
  // Only create promise once per id:
  const dataPromise = useMemo(() => 
    fetch(`/api/data/${id}`).then(r => r.json()),
    [id]
  );
  
  const data = use(dataPromise);
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

function App() {
  const [id, setId] = useState(1);
  
  return (
    <Suspense fallback={<Spinner />}>
      <DataComponent id={id} />
      <button onClick={() => setId(id + 1)}>Next</button>
    </Suspense>
  );
}

Pattern 3: Library Integration (React Query)

Modern data libraries support Suspense directly. React Query 5+ example:

// React Query 5+ with Suspense:
import { useSuspenseQuery } from '@tanstack/react-query';

function UserProfile({ userId }) {
  // useSuspenseQuery throws promise if suspended
  const { data: user } = useSuspenseQuery({
    queryKey: ['user', userId],
    queryFn: () => fetchUser(userId),
  });
  
  return <div>{user.name}</div>;
}

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <UserProfile userId={123} />
    </Suspense>
  );
}

Advantage: Library handles caching, retries, and cache invalidation.


Pattern 4: Error Boundary Integration

Combine Suspense with Error Boundary to handle both loading and errors:

function UserProfile({ userId }) {
  const user = use(fetchUser(userId)); // Suspends while loading
  return <div>{user.name}</div>;
}

function App() {
  return (
    <ErrorBoundary fallback={<ErrorScreen />}>
      <Suspense fallback={<Spinner />}>
        <UserProfile userId={123} />
      </Suspense>
    </ErrorBoundary>
  );
}

class ErrorBoundary extends React.Component {
  state = { error: null };
  
  static getDerivedStateFromError(error) {
    return { error };
  }
  
  render() {
    if (this.state.error) return this.props.fallback;
    return this.props.children;
  }
}

Nested Suspense Boundaries

Use multiple Suspense boundaries to show partial UI while waiting for different data:

function App({ userId }) {
  return (
    <div>
      <Suspense fallback={<UserSpinner />}>
        <UserProfile userId={userId} />
      </Suspense>
      
      <Suspense fallback={<PostsSpinner />}>
        <UserPosts userId={userId} />
      </Suspense>
    </div>
  );
}

function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <h1>{user.name}</h1>;
}

function UserPosts({ userId }) {
  const posts = use(fetchUserPosts(userId));
  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

Now:

  • User profile shows spinner while loading
  • Posts show spinner independently
  • Both can render as they complete

Sequential vs Parallel Suspense

Sequential (wait for first before fetching second)

function App({ userId }) {
  const user = use(fetchUser(userId)); // Must complete first
  
  return (
    <Suspense fallback={<PostsSpinner />}>
      <UserPosts userId={user.id} /> {/* Depends on user */}
    </Suspense>
  );
}

function UserPosts({ userId }) {
  const posts = use(fetchUserPosts(userId));
  return <ul>{posts.map(p => <li>{p.title}</li>)}</ul>;
}

Parallel (fetch both at once)

function App({ userId }) {
  return (
    <div>
      <Suspense fallback={<UserSpinner />}>
        <UserProfile userId={userId} />
      </Suspense>
      
      <Suspense fallback={<PostsSpinner />}>
        <UserPosts userId={userId} /> {/* Fetches in parallel */}
      </Suspense>
    </div>
  );
}

Migration Strategy for React 18 → React 19

Phase 1 No changes required

Suspense is still optional and experimental for data fetching. All existing useEffect + state patterns continue to work.

Phase 2 Wait for stability

Before adopting Suspense data fetching in production:

  • Wait for React 19 to ship (not preview)
  • Verify your data library supports Suspense
  • Plan migration after app stabilizes on React 19 core

Phase 3 Refactor to Suspense (optional, post-preview)

Once stable, profile candidates:

grep -rn "useEffect.*fetch\|useEffect.*axios\|useEffect.*graphql" src/ --include="*.js" --include="*.jsx"
// Before (React 18):
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  if (!user) return <Spinner />;
  return <div>{user.name}</div>;
}

// After (React 19 with Suspense):
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <div>{user.name}</div>;
}

// Must be wrapped in Suspense:
<Suspense fallback={<Spinner />}>
  <UserProfile userId={123} />
</Suspense>

Important Warnings

  1. Still Preview Suspense for data is marked experimental, behavior may change
  2. Performance promises are recreated on every render without memoization; use useMemo
  3. Cache use() doesn't cache; use React Query or similar for production apps
  4. SSR Suspense SSR support is limited; check Next.js version requirements

Source: SKILL.md on GitHub

No alerts15d4 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    The skill is a collection of React 19 migration references and documentation. It contains standard coding patterns, migration guidelines, and tool definitions consistent with its stated purpose. No malicious patterns or security risks were identified.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 90921cc. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 20 hours ago.

Activeupdated 5 months ago
  • React
  • react19
  • concurrent
  • utransition
  • usedeferredvalue
  • suspense
  • use-hook
  • actions
  • migration

README badge

README badge for github/awesome-copilot/react19-concurrent-patterns

Instructs Claude on which React 18 concurrent patterns to preserve during React 19 migration and which new React 19 APIs (use(), Actions, useOptimistic, Suspense for data) to adopt afterward. Covers useTransition, useDeferredValue, Suspense, createRoot, and verifies concurrent-mode logic remains untouched during the upgrade.

Generated from the current SKILL.md.

Does this skill cover the actual React 18 to React 19 migration?
No. This skill assumes the React 18 to React 19 migration (createRoot, API updates) is already done by another tool. It focuses on preserving existing concurrent patterns during migration and adopting new React 19 APIs afterward.
Should I adopt React 19 APIs like use() and useOptimistic during the migration?
No. The skill recommends stabilizing the migration first, then introducing new APIs in a separate cleanup sprint after the codebase is stable on React 19.
Do useTransition, useDeferredValue, and Suspense need changes for React 19?
No. These React 18 concurrent patterns work identically in React 19 and should not be touched during migration.
What new React 19 APIs does this skill cover?
The skill references use(), Actions, useActionState, useFormStatus, useOptimistic, and Suspense for data fetching as patterns worth adopting post-migration.

Generated from the current SKILL.md. These answers refresh after source changes.