--- name: lang-react description: Build React SPAs where components are declarative UI consuming external state (Zustand/XState/TanStack Query). Logic lives in stores, not components. --- # React Expert ## Core Philosophy Components consume external state, contain no logic: - External hooks at top (Zustand, XState, TanStack Query) - No useState/useReducer/useEffect for complex logic - Inline actions unless repeated 2+ times - Test stores/machines (unit tests), not components (E2E only) ## State Management Stack | State Type | Solution | | --------------------- | ---------------------------- | | **Remote (REST)** | TanStack Query | | **Remote (GraphQL)** | Apollo Client | | **Application state** | Zustand | | **Complex machines** | XState | | **Local UI state** | useState (rare, last resort) | ## Component Pattern ```typescript // ✅ External hooks → Early returns → JSX function UserProfile({ userId }: { userId: string }) { const { user, isLoading } = useUser(userId); const { updateProfile } = useUserActions(); if (isLoading) return ; return (

{user.name}

); } ``` ## Testing: Stores, Not Components | What | Tool | Why | | --------------- | ---------- | ------------------------- | | Zustand stores | Vitest | Test without React | | XState machines | Vitest | Deterministic transitions | | Critical flows | Playwright | Real browser | | Components | Never | Logic should be in stores | ```typescript // Test store directly const { login } = useAuthStore.getState(); await login({ email: "test@example.com", password: "pass" }); expect(useAuthStore.getState().user).toBeDefined(); ``` ## Unique Patterns ### Prop Ordering: Simple → Complex ```tsx {item.name}} /> ``` ### Inline Props for Type Inference ```tsx // ✅ Inline - TypeScript infers types } />; // ❌ Extract only if repeated 2+ times const renderItem = (budget: Budget) => ; ``` ### Pattern Matching for Variants ```tsx import { match } from "ts-pattern"; { match(state) .with({ _tag: "loading" }, () => ) .with({ _tag: "success" }, (s) => ) .exhaustive(); } ``` ### Performance: Profile First | Technique | When | | -------------- | ------------------- | | useMemo | Profiled as slow | | useCallback | Repeated 2+ times | | React.memo | Props rarely change | | Code splitting | Route-level | ### Styled-Components: Consolidate with CSS Selectors ```typescript // ✅ Single component with CSS selectors const Table = styled.table` thead { background: ${(p) => p.theme.colors.header}; } tbody tr:hover { background: ${(p) => p.theme.colors.hover}; } td { padding: ${(p) => p.theme.space.md}; } `; // ❌ Separate components for each element const TableHeader = styled.thead`...`; const TableRow = styled.tr`...`; const TableCell = styled.td`...`; ``` ## Version 1.0.1