mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-14 07:13:35 +08:00
* feat: expand Kiro adapter to full language coverage - Add 17 new agents (typescript, rust, kotlin, java, cpp, django, swift, fsharp, pytorch, mle, performance-optimizer) in both .md and .json formats - Add 25 new skills (rust, kotlin, java/spring, django, fastapi, nestjs, react, nextjs, cpp, swift, mle/pytorch, deep-research, strategic-compact, autonomous-loops, content-hash-cache-pattern) - Add 6 new language-specific steering files (rust, kotlin, java, cpp, php, ruby) - Add 3 new hooks (rust-check-on-edit, python-lint-on-edit, security-check-on-create) - Update README with expanded component inventory and documentation - Fix install.sh line endings for macOS compatibility Total Kiro components: 33 agents, 43 skills, 22 steering files, 13 hooks * fix: resolve P1/P2 violations in Kiro agents, skills, and steering - java-patterns.md: remove reference to non-existent quarkus-patterns skill - kotlin-patterns.md: fix insecure BuildConfig recommendation for secrets - swift-actor-persistence: fix Swift version claim (5.9+) and Dictionary crash - java-reviewer.md: add recursive framework detection + robust diff chain - kotlin-reviewer.md: replace unreliable diff detection with fallback chain - rust-reviewer.md: add diff fallback + make CI gating mandatory - jpa-patterns: add DISTINCT to fetch-join query to prevent duplicates - django-reviewer.md: add migration safety check, narrow save() rule, fix pytest-django behavior description * fix: resolve remaining violations in Kiro agents, skills, and docs Agents: - java-build-resolver.md: remove quarkus-patterns ref, fix 'Initialise' spelling - java-reviewer.json: remove quarkus-patterns ref from prompt - mle-reviewer.md, cpp-build-resolver.md, java-build-resolver.md, performance-optimizer.md: fix allowedTools 'read' -> 'fs_read' Hooks: - rust-check-on-edit: fix description to match askAgent behavior Skills: - content-hash-cache-pattern: hyphenate 'Content-Hash-Based' - cpp-testing: hyphenate 'real-time' - django-security: use placeholder secrets, fix CSRF_COOKIE_HTTPONLY=False - nestjs-patterns: add Logger to HttpExceptionFilter for non-Http errors - react-patterns: add React 19 compatibility note for useActionState - rust-patterns: remove edition-specific 'Rust 2024+' reference - springboot-patterns: cap exponential backoff, recommend Resilience4j - springboot-security: fix invalid @Query SQL injection example - swift-protocol-di-testing: add thread-safety doc comment to mock Docs: - README.md: fix Project Structure counts (33/43/22/13) * fix: sync README tree with counts, restore local diff in kotlin-reviewer, correct django FK index guidance - README.md: Project Structure tree now lists all 33 agents, 43 skills, 22 steering files, and 13 hooks (was showing old subset) - kotlin-reviewer.md: restore git diff --staged / git diff for local pre-commit review before falling back to HEAD~1 - django-reviewer.md: clarify that ForeignKey fields are indexed by default; only flag missing db_index on non-FK filter columns
344 lines
11 KiB
Markdown
344 lines
11 KiB
Markdown
---
|
|
name: react-patterns
|
|
description: React 18/19 patterns including hooks discipline, server/client component boundaries, Suspense + error boundaries, form actions, data fetching, state management decision trees, and accessibility-first composition. Use when writing or reviewing React components.
|
|
origin: ECC
|
|
---
|
|
|
|
# React Patterns
|
|
|
|
Idiomatic React 18/19 patterns for building robust, accessible, performant component trees.
|
|
|
|
## When to Activate
|
|
|
|
- Writing or modifying React function components, custom hooks, or component trees
|
|
- Reviewing JSX/TSX files
|
|
- Designing state shape or component composition
|
|
- Migrating class components or older `forwardRef`/`useEffect`-heavy code
|
|
- Choosing between local state, lifted state, context, and external stores
|
|
- Working with Server Components / Client Components (Next.js App Router, RSC)
|
|
- Implementing forms with React 19 actions or controlled inputs
|
|
- Wiring data fetching with TanStack Query / SWR / RSC
|
|
|
|
## Core Principles
|
|
|
|
### 1. Render is a Pure Function of Props and State
|
|
|
|
```tsx
|
|
// Good: derive during render
|
|
function Cart({ items }: { items: CartItem[] }) {
|
|
const total = items.reduce((sum, i) => sum + i.price * i.qty, 0);
|
|
return <span>{formatMoney(total)}</span>;
|
|
}
|
|
|
|
// Bad: derived state stored separately
|
|
function Cart({ items }: { items: CartItem[] }) {
|
|
const [total, setTotal] = useState(0);
|
|
useEffect(() => {
|
|
setTotal(items.reduce((sum, i) => sum + i.price * i.qty, 0));
|
|
}, [items]);
|
|
return <span>{formatMoney(total)}</span>;
|
|
}
|
|
```
|
|
|
|
Derived state in `useEffect` adds a render cycle, can desync, and obscures the data flow.
|
|
|
|
### 2. Side Effects Outside Render
|
|
|
|
Effects, mutations, network calls, and subscriptions live in event handlers or `useEffect` — never in the render body.
|
|
|
|
### 3. Composition Over Inheritance
|
|
|
|
React has no inheritance model for components. Compose with `children`, render props, or component props.
|
|
|
|
## Hooks Discipline
|
|
|
|
See [rules/react/hooks.md](../../rules/react/hooks.md) for the full ruleset. Highlights:
|
|
|
|
- Top-level only, never conditional
|
|
- Cleanup every subscription, interval, listener
|
|
- Functional updater (`setX(prev => prev + 1)`) when new state depends on old
|
|
- Default position: do not memoize — add `useMemo`/`useCallback` only when a profiler or a dependency chain proves it matters
|
|
- Extract a custom hook only when the same hook sequence appears in 2+ components
|
|
|
|
## State Location Decision Tree
|
|
|
|
```
|
|
Used by one component?
|
|
-> useState inside it
|
|
|
|
Used by parent + a few descendants?
|
|
-> lift to nearest common ancestor
|
|
|
|
Used across distant branches AND low-frequency reads (theme, auth, locale)?
|
|
-> React Context
|
|
|
|
High-frequency updates shared across the tree?
|
|
-> external store (Zustand, Jotai, Redux Toolkit)
|
|
|
|
Derived from a server?
|
|
-> server-state library (TanStack Query, SWR, RSC fetch)
|
|
```
|
|
|
|
Most pages do not need context or a global store. Resist abstraction until duplicated lifting becomes painful.
|
|
|
|
## Server / Client Components (RSC)
|
|
|
|
```tsx
|
|
// Server Component - default, async, never ships JS for itself
|
|
export default async function ProductPage({ params }: { params: { id: string } }) {
|
|
const product = await db.product.findUnique({ where: { id: params.id } });
|
|
if (!product) notFound();
|
|
return <ProductView product={product} />;
|
|
}
|
|
|
|
// Client Component - opt in with "use client"
|
|
"use client";
|
|
export function AddToCartButton({ productId }: { productId: string }) {
|
|
const [pending, startTransition] = useTransition();
|
|
return (
|
|
<button
|
|
disabled={pending}
|
|
onClick={() => startTransition(() => addToCart(productId))}
|
|
>
|
|
{pending ? "Adding..." : "Add to cart"}
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
Boundaries:
|
|
|
|
- Server -> Client: pass serializable props or `children`
|
|
- Client -> Server: invoke Server Actions via `<form action={...}>` or imperatively from event handlers
|
|
- Never `import` a Server Component from a Client Component file — compose them via `children` instead
|
|
|
|
## Suspense + Error Boundaries
|
|
|
|
```tsx
|
|
<ErrorBoundary fallback={<ErrorView />}>
|
|
<Suspense fallback={<UserSkeleton />}>
|
|
<UserDetail id={id} />
|
|
</Suspense>
|
|
</ErrorBoundary>
|
|
```
|
|
|
|
- Place Suspense boundaries close to the data, not at the route root — progressively reveal content
|
|
- Error Boundary remains a class API; use `react-error-boundary` for a hook-friendly wrapper
|
|
- A boundary catches errors thrown during render, lifecycle, and constructors of its children — NOT in event handlers or async code
|
|
|
|
## Forms
|
|
|
|
### React 19 form actions (preferred for new code)
|
|
|
|
> **React 19+**: This example uses `useActionState`. For React 18, use `useFormState` from `react-dom` instead.
|
|
|
|
```tsx
|
|
"use client";
|
|
import { useActionState } from "react";
|
|
|
|
const initial = { error: null as string | null };
|
|
|
|
async function updateUserAction(_prev: typeof initial, formData: FormData) {
|
|
"use server";
|
|
const parsed = UserSchema.safeParse(Object.fromEntries(formData));
|
|
if (!parsed.success) return { error: "Invalid input" };
|
|
await db.user.update({ where: { id: parsed.data.id }, data: parsed.data });
|
|
return { error: null };
|
|
}
|
|
|
|
export function UserForm() {
|
|
const [state, formAction, pending] = useActionState(updateUserAction, initial);
|
|
return (
|
|
<form action={formAction}>
|
|
<input name="name" required />
|
|
<button type="submit" disabled={pending}>Save</button>
|
|
{state.error && <p role="alert">{state.error}</p>}
|
|
</form>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Controlled inputs
|
|
|
|
Use controlled when the value drives other UI, formats on every keystroke, or implements real-time validation.
|
|
|
|
### Complex forms
|
|
|
|
For multi-step forms, dynamic field arrays, or cross-field validation: use a library (React Hook Form, TanStack Form). Roll-your-own state management for forms past trivial complexity is a maintenance trap.
|
|
|
|
## Data Fetching Decision Matrix
|
|
|
|
| Need | Tool |
|
|
|---|---|
|
|
| Per-request data in Next.js App Router | RSC `await fetch()` |
|
|
| Client-side cache + mutations + invalidation | TanStack Query |
|
|
| Lightweight client cache + revalidation | SWR |
|
|
| Real-time subscriptions | Server-Sent Events, WebSockets, or the lib's subscription API |
|
|
| One-off fire-and-forget | `fetch()` in an event handler |
|
|
|
|
Avoid `useEffect` + `fetch` for application data — race conditions, no cache, no retry, no Suspense integration.
|
|
|
|
## Composition Recipes
|
|
|
|
### Slot via `children`
|
|
|
|
```tsx
|
|
<Layout>
|
|
<Header />
|
|
<Main>{content}</Main>
|
|
</Layout>
|
|
```
|
|
|
|
### Named slots
|
|
|
|
```tsx
|
|
<Page header={<Nav />} sidebar={<Filters />}>
|
|
<Results />
|
|
</Page>
|
|
```
|
|
|
|
### Compound components (shared state via Context)
|
|
|
|
```tsx
|
|
<Tabs defaultValue="profile">
|
|
<Tabs.List>
|
|
<Tabs.Trigger value="profile">Profile</Tabs.Trigger>
|
|
<Tabs.Trigger value="settings">Settings</Tabs.Trigger>
|
|
</Tabs.List>
|
|
<Tabs.Panel value="profile"><Profile /></Tabs.Panel>
|
|
<Tabs.Panel value="settings"><Settings /></Tabs.Panel>
|
|
</Tabs>
|
|
```
|
|
|
|
### Render prop / function-as-child
|
|
|
|
Useful when the parent needs to pass parameters to the rendered output:
|
|
|
|
```tsx
|
|
<DataLoader id={id}>
|
|
{({ data, isLoading }) => isLoading ? <Spinner /> : <UserCard user={data} />}
|
|
</DataLoader>
|
|
```
|
|
|
|
Modern alternative: a hook (`useData(id)`) returning the same shape — usually cleaner.
|
|
|
|
## Performance
|
|
|
|
### When `React.memo` Actually Helps
|
|
|
|
Wrap a component in `React.memo` only when:
|
|
|
|
1. It re-renders frequently
|
|
2. Its props are usually the same between renders
|
|
3. Its render is measurably expensive
|
|
|
|
`React.memo` adds an equality check on every render. If props differ on most renders, the check is pure overhead.
|
|
|
|
### Avoiding Render Cascades
|
|
|
|
- Lift state down rather than up where possible
|
|
- Split context: one context per concern, so a change to `themeContext` does not re-render auth consumers
|
|
- Use `useSyncExternalStore` for external state libraries — required for safe concurrent rendering
|
|
|
|
### Lists
|
|
|
|
- Provide stable `key` props (database id, not array index)
|
|
- Virtualize long lists with `@tanstack/react-virtual` or `react-window` once visible item count exceeds ~50 with non-trivial rows
|
|
|
|
## Accessibility-First Composition
|
|
|
|
- Always render semantic HTML (`<button>`, `<a>`, `<nav>`, `<main>`) before reaching for `role` attributes
|
|
- Every interactive element must be reachable by keyboard
|
|
- Form inputs need labels — `<label htmlFor>` or `aria-label` if visually labeled by an icon
|
|
- Manage focus on route changes and modal open/close
|
|
- Run `axe` in component tests (see [skills/react-testing](../react-testing/SKILL.md))
|
|
- Cross-link: [skills/accessibility/SKILL.md](../accessibility/SKILL.md) covers WCAG criteria and pattern libraries
|
|
|
|
## Routing
|
|
|
|
This skill is router-agnostic. The patterns above work with React Router, TanStack Router, Next.js App Router, Remix Router. Router-specific patterns (loaders, actions, nested layouts) follow the router's documentation — those are framework concerns layered on top of React core.
|
|
|
|
## Out of Scope (Pointer Sections)
|
|
|
|
- **Next.js specifics**: App Router data loading, Route Handlers, Middleware, Parallel Routes — separate concern, use Next.js docs
|
|
- **React Native**: Platform-specific patterns differ enough to warrant a separate `react-native-patterns` skill (not present yet)
|
|
- **Remix**: Loader/action conventions overlap with RSC but follow Remix docs
|
|
|
|
## Related
|
|
|
|
- Rules: [rules/react/](../../rules/react/) — coding-style, hooks, patterns, security, testing
|
|
- Skills: [react-performance](../react-performance/SKILL.md) for the Vercel-derived performance ruleset, [frontend-patterns](../frontend-patterns/SKILL.md) for cross-framework UI concerns, [accessibility](../accessibility/SKILL.md), [angular-developer](../angular-developer/SKILL.md) for framework comparison
|
|
- Agents: `react-reviewer` for code review, `react-build-resolver` for build/bundler errors
|
|
- Commands: `/react-review`, `/react-build`, `/react-test`
|
|
|
|
## Examples
|
|
|
|
### Custom hook for debounced search
|
|
|
|
```tsx
|
|
function useDebounce<T>(value: T, delay = 300): T {
|
|
const [debounced, setDebounced] = useState(value);
|
|
useEffect(() => {
|
|
const id = setTimeout(() => setDebounced(value), delay);
|
|
return () => clearTimeout(id);
|
|
}, [value, delay]);
|
|
return debounced;
|
|
}
|
|
|
|
function SearchBox() {
|
|
const [query, setQuery] = useState("");
|
|
const debounced = useDebounce(query, 300);
|
|
const { data } = useQuery({
|
|
queryKey: ["search", debounced],
|
|
queryFn: () => searchApi(debounced),
|
|
enabled: debounced.length > 0,
|
|
});
|
|
return (
|
|
<>
|
|
<input value={query} onChange={(e) => setQuery(e.target.value)} />
|
|
<Results items={data ?? []} />
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Optimistic UI with React 19 `useOptimistic`
|
|
|
|
```tsx
|
|
"use client";
|
|
import { useOptimistic } from "react";
|
|
|
|
export function MessageList({ messages }: { messages: Message[] }) {
|
|
const [optimistic, addOptimistic] = useOptimistic(
|
|
messages,
|
|
(state, newMessage: Message) => [...state, newMessage],
|
|
);
|
|
|
|
async function send(formData: FormData) {
|
|
const text = String(formData.get("text"));
|
|
addOptimistic({ id: "pending", text, sender: "me" });
|
|
await saveMessage(text);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<ul>{optimistic.map((m) => <li key={m.id}>{m.text}</li>)}</ul>
|
|
<form action={send}>
|
|
<input name="text" />
|
|
<button type="submit">Send</button>
|
|
</form>
|
|
</>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Splitting context to avoid render cascades
|
|
|
|
```tsx
|
|
// Two contexts: one rarely changes, one frequently
|
|
const ThemeContext = createContext<Theme>("light");
|
|
const NotificationsContext = createContext<Notification[]>([]);
|
|
|
|
// A component that only consumes ThemeContext does NOT re-render when notifications change
|
|
```
|