Claude-skills react-patterns
React 19 performance patterns and composition architecture for Vite + Cloudflare projects. 50+ rules ranked by impact — eliminating waterfalls, bundle optimisation, re-render prevention, composition over boolean props, server/client boundaries, and React 19 APIs. Use when writing, reviewing, or refactoring React components. Triggers: 'react patterns', 'react review', 'react performance', 'optimise components', 'react best practices', 'composition patterns', 'why is it slow', 'reduce re-renders', 'fix waterfall'.
git clone https://github.com/jezweb/claude-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/jezweb/claude-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/frontend/skills/react-patterns" ~/.claude/skills/jezweb-claude-skills-react-patterns && rm -rf "$T"
plugins/frontend/skills/react-patterns/SKILL.mdReact Patterns
Performance and composition patterns for React 19 + Vite + Cloudflare Workers projects. Use as a checklist when writing new components, a review guide when auditing existing code, or a refactoring playbook when something feels slow or tangled.
Rules are ranked by impact. Fix CRITICAL issues before touching MEDIUM ones.
When to Apply
- Writing new React components or pages
- Reviewing code for performance issues
- Refactoring components with too many props or re-renders
- Debugging "why is this slow?" or "why does this re-render?"
- Building reusable component libraries
- Code review before merging
1. Eliminating Waterfalls (CRITICAL)
Sequential async calls where they could be parallel. The #1 performance killer.
| Pattern | Problem | Fix |
|---|---|---|
| Await in sequence | | |
| Fetch in child | Parent renders, then child fetches, then grandchild fetches | Hoist fetches to the highest common ancestor, pass data down |
| Suspense cascade | Multiple Suspense boundaries that resolve sequentially | One Suspense boundary wrapping all async siblings |
| Await before branch | | Move await inside the branch — don't fetch what you might not use |
| Import then render | | Use + — renders fallback instantly |
How to find them: Search for
await in components. Each await is a potential waterfall. If two awaits are independent, they should be parallel.
2. Bundle Size (CRITICAL)
Every KB the user downloads is a KB they wait for.
| Pattern | Problem | Fix |
|---|---|---|
| Barrel imports | pulls the entire barrel file | — direct import |
| No code splitting | Heavy component loaded on every page | + |
| Third-party at load | Analytics/tracking loaded before the app renders | Load after hydration: |
| Full library import | (70KB) | (1KB) |
| Lucide tree-shaking | (all icons) | Explicit map: |
| Duplicate React | Library bundles its own React → "Cannot read properties of null" | in vite.config.ts |
How to find them:
npx vite-bundle-visualizer — shows what's in your bundle.
3. Composition Architecture (HIGH)
How you structure components matters more than how you optimise them.
| Pattern | Problem | Fix |
|---|---|---|
| Boolean prop explosion | | Explicit variants: , |
| Compound components | Complex component with 15 props | Split into , , with shared context |
| renderX props | | Use children + named slots: |
| Lift state | Sibling components can't share state | Move state to parent or context provider |
| Provider implementation | Consumer code knows about state management internals | Provider exposes interface — implementation hidden |
| Inline components | | Define Child outside Parent — inline components remount on every render |
The test: If a component has more than 5 boolean props, it needs composition, not more props.
4. Re-render Prevention (MEDIUM)
Not all re-renders are bad. Only fix re-renders that cause visible jank or wasted computation.
| Pattern | Problem | Fix |
|---|---|---|
| Default object/array props | → new array ref every render | Hoist: |
| Derived state in effect | | Derive during render: |
| Object dependency | fires every render if config is | Use primitive deps: |
| Subscribe to unused state | Component reads but only uses | Split context or use selector: |
| State for transient values | on mousemove | Use for values that change frequently but don't need re-render |
| Inline callback props | — new function every render | or functional setState: |
How to find them: React DevTools Profiler → "Why did this render?" or
<React.StrictMode> double-renders in dev.
5. React 19 Specifics (MEDIUM)
Patterns that changed or are new in React 19.
| Pattern | Old (React 18) | New (React 19) |
|---|---|---|
| Form state | | — renamed |
| Ref forwarding | | — ref is a regular prop |
| Context | | — works in conditionals and loops |
| Pending UI | Manual loading state | + for non-urgent updates |
| Route-level lazy | Works with only | Still true — is silently ignored with |
| Optimistic updates | Manual state management | hook |
| Metadata | Helmet or manual management | , , in component JSX — hoisted to automatically |
6. Rendering Performance (MEDIUM)
| Pattern | Problem | Fix |
|---|---|---|
| Layout shift on load | Content jumps when async data arrives | Skeleton screens matching final layout dimensions |
| Animate SVG directly | Janky SVG animation | Wrap in , animate the div instead |
| Large list rendering | 1000+ items in a table/list | for virtualised rendering |
| content-visibility | Long scrollable content renders everything upfront | on off-screen sections |
| Conditional render with && | renders when count is 0 | Use ternary: |
7. Data Fetching (MEDIUM)
| Pattern | Problem | Fix |
|---|---|---|
| No deduplication | Same data fetched by 3 components | TanStack Query or SWR — automatic dedup + caching |
| Fetch on mount | — waterfalls, no caching, no dedup | TanStack Query: |
| No optimistic update | User clicks save, waits 2 seconds, then sees change | with for instant visual feedback |
| Stale closure in interval | captures stale state | for the interval ID and current values |
| Polling without cleanup | in useEffect without | Return cleanup: |
8. Vite + Cloudflare Specifics (MEDIUM)
| Pattern | Problem | Fix |
|---|---|---|
in Node scripts | Undefined — only works in Vite-processed files | Use from vite |
| React duplicate instance | Library bundles its own React | + in vite.config.ts |
| Radix Select empty string | throws | Use sentinel: |
| React Hook Form null | passes null to Input | Spread manually: |
| Env vars at edge | doesn't exist in Workers | Use (Hono context) or (Vite build-time) |
Using as a Review Checklist
When reviewing code, go through categories 1-3 (CRITICAL + HIGH) for every PR. Categories 4-8 only when performance is a concern.
/react-patterns [file or component path]
Read the file, check against rules in priority order, report findings as:
file:line — [rule] description of issue