react-data-fetching
Teaches modern React data fetching patterns with TanStack Query, SWR, and Suspense. Use when implementing caching, deduplication, optimistic updates, or parallel loading in React applications.
What this skill does
# React Data Fetching Patterns
## Table of Contents
- [When to Use](#when-to-use)
- [Instructions](#instructions)
- [Details](#details)
- [Source](#source)
Production-ready patterns for fetching, caching, and synchronizing server data in React applications. These patterns are framework-agnostic — they work whether you're using Vite + React Router, Next.js, Remix, or a custom setup.
## When to Use
Reference these patterns when:
- Adding data fetching to components
- Replacing `useEffect` + `fetch` with a proper data layer
- Implementing caching, deduplication, or optimistic updates
- Debugging waterfall loading patterns
- Choosing between data fetching libraries
## Instructions
- Apply these patterns during code generation, review, and refactoring. When you see fetch-in-effect without caching or deduplication, suggest the appropriate pattern.
## Details
### Overview
The most common performance problem in React apps is **request waterfalls** — sequential fetches that could run in parallel. The second most common problem is **redundant fetches** — multiple components fetching the same data independently. The patterns below address both, starting with the highest-impact fixes.
---
### 1. Parallelize Independent Fetches with `Promise.all`
**Impact: CRITICAL** — Eliminates sequential waterfalls for 2-10x improvement.
When multiple fetches have no dependencies on each other, run them concurrently.
**Avoid — sequential (3 round trips):**
```typescript
async function loadDashboard() {
const user = await fetchUser()
const posts = await fetchPosts()
const notifications = await fetchNotifications()
return { user, posts, notifications }
}
```
**Prefer — parallel (1 round trip):**
```typescript
async function loadDashboard() {
const [user, posts, notifications] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchNotifications(),
])
return { user, posts, notifications }
}
```
When fetches have partial dependencies (B depends on A, but C doesn't), start independent work immediately:
```typescript
async function loadPage() {
const userPromise = fetchUser()
const configPromise = fetchConfig()
const user = await userPromise
const [config, posts] = await Promise.all([
configPromise,
fetchPosts(user.id), // depends on user
])
return { user, config, posts }
}
```
---
### 2. Defer Await Until the Value Is Needed
**Impact: HIGH** — Starts work earlier without blocking on results you don't need yet.
A common mistake is to `await` each promise immediately, even when subsequent code doesn't need the result right away. Start the promise early, then `await` it at the point where you actually read the value.
**Avoid — blocks unnecessarily:**
```typescript
async function loadProfile(userId: string) {
const user = await fetchUser(userId) // waits here
const prefs = await fetchPreferences() // starts only after user resolves
const avatar = buildAvatarUrl(user.avatar)
return { user, prefs, avatar }
}
```
**Prefer — start early, await late:**
```typescript
async function loadProfile(userId: string) {
const userPromise = fetchUser(userId) // starts immediately
const prefsPromise = fetchPreferences() // starts immediately
const user = await userPromise // await when needed
const avatar = buildAvatarUrl(user.avatar)
const prefs = await prefsPromise // may already be resolved
return { user, prefs, avatar }
}
```
This is complementary to `Promise.all` — use defer-await when you need intermediate results between fetches, and `Promise.all` when you can wait for everything at once.
---
### 3. Use TanStack Query for Client-Side Data
**Impact: CRITICAL** — Automatic caching, deduplication, revalidation, and error handling.
Raw `useEffect` + `fetch` lacks caching, deduplication, retry, and background refresh. Use a data fetching library.
**Avoid — no caching, no dedup, no error handling:**
```tsx
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
setLoading(true)
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(setUser)
.finally(() => setLoading(false))
}, [userId])
if (loading) return <Skeleton />
return <div>{user?.name}</div>
}
```
**Prefer — TanStack Query (recommended for Vite + React apps):**
```tsx
import { useQuery } from '@tanstack/react-query'
function UserProfile({ userId }: { userId: string }) {
const { data: user, isLoading } = useQuery({
queryKey: ['user', userId],
queryFn: () => fetch(`/api/users/${userId}`).then(r => r.json()),
})
if (isLoading) return <Skeleton />
return <div>{user?.name}</div>
}
```
TanStack Query is the strongest choice for Vite apps — it's framework-agnostic, has built-in `useSuspenseQuery`, devtools, infinite queries, optimistic mutations, and offline support. SWR is a lighter alternative that covers the basics (dedup, caching, revalidation) but has fewer features for complex mutation workflows.
Both give you: request deduplication, stale-while-revalidate caching, automatic retries, and background refresh.
**Setup for Vite apps:**
```tsx
// main.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60_000, // 1 minute
retry: 2,
},
},
})
createRoot(document.getElementById('root')!).render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
)
```
---
### 4. Use Suspense for Declarative Loading States
**Impact: HIGH** — Cleaner code, automatic loading coordination, streaming support.
Suspense lets you declare loading boundaries in the component tree instead of managing `isLoading` state in every component.
**Avoid — manual loading orchestration:**
```tsx
function Dashboard() {
const { data: user, isLoading: userLoading } = useQuery(userQuery)
const { data: stats, isLoading: statsLoading } = useQuery(statsQuery)
if (userLoading || statsLoading) return <FullPageSpinner />
return (
<div>
<UserHeader user={user} />
<StatsPanel stats={stats} />
</div>
)
}
```
**Prefer — Suspense boundaries:**
```tsx
function Dashboard() {
return (
<Suspense fallback={<FullPageSpinner />}>
<DashboardContent />
</Suspense>
)
}
function DashboardContent() {
const { data: user } = useSuspenseQuery(userQuery)
const { data: stats } = useSuspenseQuery(statsQuery)
return (
<div>
<UserHeader user={user} />
<StatsPanel stats={stats} />
</div>
)
}
```
For independent sections, use separate Suspense boundaries so they load independently:
```tsx
function Dashboard() {
return (
<div>
<Suspense fallback={<HeaderSkeleton />}>
<UserHeader />
</Suspense>
<Suspense fallback={<StatsSkeleton />}>
<StatsPanel />
</Suspense>
</div>
)
}
```
TanStack Query provides `useSuspenseQuery` and SWR provides `{ suspense: true }` option.
---
### 5. Prefetch Data Before Navigation
**Impact: HIGH** — Eliminates loading states on page transitions.
Start fetching data before the user commits to a navigation — on hover, focus, or route preload.
**With TanStack Query:**
```tsx
import { useQueryClient } from '@tanstack/react-query'
function ProjectLink({ projectId }: { projectId: string }) {
const queryClient = useQueryClient()
const prefetch = () => {
queryClient.prefetchQuery({
queryKey: ['project', projectId],
queryFn: () => fetchProject(projectId),
staleTime: 30_000,
})
}
return (
<Link
to={`/projects/${projectId}`}
onMouseEnter={prefetch}
onFocus={prefetch}
>
View Project
</Link>
)
}
```
**With React Router loaders (Vite apps):**
```tsx
// routes.tsx
const routes = [
{
path: '/projects/:id',
loader: Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.