$npx -y skills add jezweb/claude-skills --skill react-patternsReact 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
| 1 | # React Patterns |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | Rules are ranked by impact. Fix CRITICAL issues before touching MEDIUM ones. |
| 6 | |
| 7 | ## When to Apply |
| 8 | |
| 9 | - Writing new React components or pages |
| 10 | - Reviewing code for performance issues |
| 11 | - Refactoring components with too many props or re-renders |
| 12 | - Debugging "why is this slow?" or "why does this re-render?" |
| 13 | - Building reusable component libraries |
| 14 | - Code review before merging |
| 15 | |
| 16 | ## 1. Eliminating Waterfalls (CRITICAL) |
| 17 | |
| 18 | Sequential async calls where they could be parallel. The #1 performance killer. |
| 19 | |
| 20 | | Pattern | Problem | Fix | |
| 21 | |---------|---------|-----| |
| 22 | | **Await in sequence** | `const a = await getA(); const b = await getB();` | `const [a, b] = await Promise.all([getA(), getB()]);` | |
| 23 | | **Fetch in child** | Parent renders, then child fetches, then grandchild fetches | Hoist fetches to the highest common ancestor, pass data down | |
| 24 | | **Suspense cascade** | Multiple Suspense boundaries that resolve sequentially | One Suspense boundary wrapping all async siblings | |
| 25 | | **Await before branch** | `const data = await fetch(); if (condition) { use(data); }` | Move await inside the branch — don't fetch what you might not use | |
| 26 | | **Import then render** | `const Component = await import('./Heavy'); return <Component />` | Use `React.lazy()` + `<Suspense>` — renders fallback instantly | |
| 27 | |
| 28 | **How to find them**: Search for `await` in components. Each `await` is a potential waterfall. If two awaits are independent, they should be parallel. |
| 29 | |
| 30 | ## 2. Bundle Size (CRITICAL) |
| 31 | |
| 32 | Every KB the user downloads is a KB they wait for. |
| 33 | |
| 34 | | Pattern | Problem | Fix | |
| 35 | |---------|---------|-----| |
| 36 | | **Barrel imports** | `import { Button } from '@/components'` pulls the entire barrel file | `import { Button } from '@/components/ui/button'` — direct import | |
| 37 | | **No code splitting** | Heavy component loaded on every page | `React.lazy(() => import('./HeavyComponent'))` + `<Suspense>` | |
| 38 | | **Third-party at load** | Analytics/tracking loaded before the app renders | Load after hydration: `useEffect(() => { import('./analytics') }, [])` | |
| 39 | | **Full library import** | `import _ from 'lodash'` (70KB) | `import debounce from 'lodash/debounce'` (1KB) | |
| 40 | | **Lucide tree-shaking** | `import * as Icons from 'lucide-react'` (all icons) | Explicit map: `import { Home, Settings } from 'lucide-react'` | |
| 41 | | **Duplicate React** | Library bundles its own React → "Cannot read properties of null" | `resolve.dedupe: ['react', 'react-dom']` in vite.config.ts | |
| 42 | |
| 43 | **How to find them**: `npx vite-bundle-visualizer` — shows what's in your bundle. |
| 44 | |
| 45 | ## 3. Composition Architecture (HIGH) |
| 46 | |
| 47 | How you structure components matters more than how you optimise them. |
| 48 | |
| 49 | | Pattern | Problem | Fix | |
| 50 | |---------|---------|-----| |
| 51 | | **Boolean prop explosion** | `<Card isCompact isClickable showBorder hasIcon isLoading>` | Explicit variants: `<CompactCard>`, `<ClickableCard>` | |
| 52 | | **Compound components** | Complex component with 15 props | Split into `<Dialog>`, `<Dialog.Trigger>`, `<Dialog.Content>` with shared context | |
| 53 | | **renderX props** | `<Layout renderSidebar={...} renderHeader={...} renderFooter={...}>` | Use children + named slots: `<Layout><Sidebar /><Header /></Layout>` | |
| 54 | | **Lift state** | Sibling components can't share state | Move state to parent or context provider | |
| 55 | | **Provider implementation** | Consumer code knows about state management internals | Provider exposes interface `{ state, actions, meta }` — implementation hidden | |
| 56 | | **Inline components** | `function Parent() { function Child() { ... } return <Child /> }` | Define Child outside Parent — inline components remount on every render | |
| 57 | |
| 58 | **The test**: If a component has more than 5 boolean props, it needs composition, not more props. |
| 59 | |
| 60 | ## 4. Re-render Prevention (MEDIUM) |
| 61 | |
| 62 | Not all re-renders are bad. Only fix re-renders that cause visible jank or wasted computation. |
| 63 | |
| 64 | | Pattern | Problem | Fix | |
| 65 | |---------|---------|-----| |
| 66 | | **Default object/array props** | `function Foo({ items = [] })` → new array ref every render | Hoist: `const DEFAULT = []; function Foo({ items = DEFAULT })` | |
| 67 | | **Derived state in effect** | `useEffect(() => setFiltered(items.filter(...)), [ |