React19 Concurrent Patterns
GitHubOfficialFirst committed by SaravananRajaramanDistributed by GitHub
- Skill
- Guidelines
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to migrating React applications to version 19. It emphasizes a two-phase strategy: first, ensuring that existing React 18 concurrent patterns remain intact and functional during the initial migration, and second, introducing new React 19 features like Actions, the use() hook, and improved Suspense patterns only after the migration is stabilized. It includes specific verification steps to prevent accidental breakage of concurrent logic.
What it does
- Validates that existing React 18 concurrent patterns are preserved during migration.
- Provides guidance on when to introduce new React 19 APIs.
- Offers patterns for using React 19 Actions with useActionState and useFormStatus.
- Demonstrates how to implement Suspense for data fetching using the use() hook.
- Includes command-line verification to check for accidental modification of concurrent patterns.
- Distinguishes between code-splitting and data-fetching use cases for Suspense.
When to use it
- When planning or executing a migration from React 18 to React 19.
- When you need to ensure concurrent mode features remain stable during a framework upgrade.
- When you are ready to refactor legacy form handling into the new React 19 Actions pattern.
- When adopting new data-fetching patterns using Suspense and the use() hook.
Advantages
- Reduces the risk of breaking concurrent features during the migration process.
- Provides a clear separation between migration stability and feature adoption.
- Simplifies form handling by replacing complex useReducer logic with Actions.
- Offers standardized patterns for modern data fetching and state management.
Usage examples
- “How do I verify that my concurrent patterns were not broken during the React 19 migration?”
- The agent provides a grep command to scan the codebase for key concurrent patterns like useTransition and useDeferredValue to ensure they remain untouched.
- “Show me how to replace my React 18 form logic with React 19 Actions.”
- The agent provides a side-by-side comparison showing the transition from useReducer to the useActionState hook.
- “How can I use Suspense for data fetching in React 19?”
- The agent explains the use() hook and provides patterns for memoizing promises to prevent unnecessary re-renders.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/github/react19-concurrent-patterns/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip react19-concurrent-patterns.zip -d .agents/skills
Documentation by SaravananRajaraman
React 19 Concurrent Patterns
React 19 introduced new APIs that complement the migration work. This skill covers two concerns:
Preserve existing React 18 concurrent patterns that must not be broken during migration
Adopt new React 19 APIs worth introducing after migration stabilizes
Part 1 Preserve: React 18 Concurrent Patterns That Must Survive the Migration
These patterns exist in React 18 codebases and must not be accidentally removed or broken:
createRoot Already Migrated by the R18 Orchestra
If the R18 orchestra already ran, ReactDOM.render → createRoot is done. Verify it's correct:
// CORRECT React 19 root (same as React 18):
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);useTransition No Migration Needed
useTransition from React 18 works identically in React 19. Do not touch these patterns during migration:
// React 18 useTransition unchanged in React 19:
const [isPending, startTransition] = useTransition();
function handleClick() {
startTransition(() => {
setFilteredResults(computeExpensiveFilter(input));
});
}useDeferredValue No Migration Needed
// React 18 useDeferredValue unchanged in React 19:
const deferredQuery = useDeferredValue(query);Suspense for Code Splitting No Migration Needed
// React 18 Suspense with lazy unchanged in React 19:
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<LazyComponent />
</Suspense>
);
}Part 2 React 19 New APIs
These are worth adopting in a post-migration cleanup sprint. Do not introduce these DURING the migration stabilize first.
For full patterns on each new API, read:
references/react19-use.mdtheuse()hook for promises and contextreferences/react19-actions.mdActions, useActionState, useFormStatus, useOptimisticreferences/react19-suspense.mdSuspense for data fetching (the new pattern)
Migration Safety Rules
During the React 19 migration itself, these concurrent-mode patterns must be left completely untouched:
# Verify nothing touched these during migration:
grep -rn "useTransition\|useDeferredValue\|Suspense\|startTransition" \
src/ --include="*.js" --include="*.jsx" | grep -v "\.test\."If the migrator touched any of these files, review the changes the migration should only have modified React API surface (forwardRef, defaultProps, etc.), never concurrent mode logic.
Files
- ATTRIBUTION.mdAdded by AutoGineer907 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md2.8 KiB
- references/react19-actions.md9.4 KiB
- references/react19-suspense.md7.6 KiB
- references/react19-use.md5.0 KiB
references/react19-suspense.md
title: React 19 Suspense for Data Fetching Pattern Reference
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 automaticallyNo need for
useEffect + statetrickCleaner 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
Still Preview Suspense for data is marked experimental, behavior may change
Performance promises are recreated on every render without memoization; use
useMemoCache
use()doesn't cache; use React Query or similar for production appsSSR Suspense SSR support is limited; check Next.js version requirements
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
SaravananRajaraman
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/react19-concurrent-patterns · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- SaravananRajaraman, stbenjam
AutoGineer is not affiliated with or endorsed by SaravananRajaraman or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download react19-concurrent-patterns.zip