Claude-skill-registry lang-react
Build React SPAs where components are declarative UI consuming external state (Zustand/XState/TanStack Query). Logic lives in stores, not components.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/lang-react" ~/.claude/skills/majiayu000-claude-skill-registry-lang-react && rm -rf "$T"
manifest:
skills/data/lang-react/SKILL.mdsource content
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
// ✅ External hooks → Early returns → JSX function UserProfile({ userId }: { userId: string }) { const { user, isLoading } = useUser(userId); const { updateProfile } = useUserActions(); if (isLoading) return <Spinner />; return ( <div> <h1>{user.name}</h1> <button onClick={() => updateProfile({ email: 'new@example.com' })}> Update </button> </div> ); }
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 |
// 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
<Table data={items} loading={isLoading} sortable onSort={handleSort} renderRow={(item) => <Row>{item.name}</Row>} />
Inline Props for Type Inference
// ✅ Inline - TypeScript infers types <SearchableList items={budgets} renderItem={(budget) => <Card name={budget.name} />} />; // ❌ Extract only if repeated 2+ times const renderItem = (budget: Budget) => <Card name={budget.name} />;
Pattern Matching for Variants
import { match } from "ts-pattern"; { match(state) .with({ _tag: "loading" }, () => <Spinner />) .with({ _tag: "success" }, (s) => <Data value={s.data} />) .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
// ✅ 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`...`;