---
name: react-idioms
description: >-
React 19+ patterns: custom hooks, Suspense boundaries, state management, component composition, and web performance. Use when developing or refactoring React components and client applications. Pair with typescript-idioms.
---
## React Idioms and Patterns
### Core Philosophy
React 19+ rewards composition, hooks, and server-aware patterns. Idiomatic React = functional, performant, accessible. Prefer co-located features, custom hooks for logic reuse, and server state libraries over hand-rolled fetch logic.
> **Scope:** This file covers React-specific coding idioms for components, hooks, state, routing, and forms. For TypeScript type system patterns, see `@.agents/skills/typescript-idioms/SKILL.md`. For file and folder layout, see `references/project-structure.md` (and the shared `@.agents/skills/frontend-design/references/frontend-layout.md`). For general frontend design, see `@.agents/skills/frontend-design/SKILL.md`.
>
> **Loading guard:** If the project uses Next.js (App Router — `app/` dir or `next.config.*`), load `@.agents/skills/nextjs-idioms/SKILL.md` **instead of** this skill for App-Router-specific patterns. This skill still applies to client components and pure-React (Vite) SPAs.
## When to Load References
> Load these **before** writing code in the matching context — not after.
| Situation | Reference to Load |
|---|---|
| Starting a React (Vite) project or reviewing file layout | `references/project-structure.md` + `@.agents/skills/frontend-design/references/frontend-layout.md` |
| TypeScript type system, async, Zod, error types | `@.agents/skills/typescript-idioms/SKILL.md` (always co-load) |
| Zod schemas / boundary validation | `@.agents/skills/typescript-idioms/references/zod-patterns.md` |
| Async / I/O / coercion pitfalls | `@.agents/skills/typescript-idioms/references/ts-patterns-and-anti-patterns.md` |
| Next.js App Router (RSC, Server Actions, caching) | `@.agents/skills/nextjs-idioms/SKILL.md` (use that skill instead for Next projects) |
---
### Component Patterns
1. **Functional components only** — no class components in new code.
2. **Composition over inheritance:**
```tsx
// ✅ Compound components
{title}
{children}
```
3. **Error boundaries** for graceful failure — wrap feature subtrees to catch render errors.
4. **Render props** for flexible, headless composition:
```tsx
{({ data, isLoading, error }) => {
if (isLoading) return ;
if (error) return ;
return ;
}}
```
5. **Props typing — always explicit:**
```tsx
// ✅ Typed props with defaults
interface TaskCardProps {
task: Task;
onComplete?: (taskId: string) => void;
variant?: 'compact' | 'expanded';
}
export function TaskCard({ task, onComplete, variant = 'compact' }: TaskCardProps) {
// ...
}
```
6. **One concern per component** — if a component exceeds ~100 JSX lines, extract a sub-component.
---
### Hooks
1. **Custom hooks for reusable logic:**
```tsx
function useTask(id: string) {
const { data, error, isLoading } = useQuery({
queryKey: ['task', id],
queryFn: () => taskApi.getTask(id),
});
return { task: data, error, isLoading };
}
```
2. **`useMemo`/`useCallback` only for measured performance issues** — not by default.
3. **`useEffect` cleanup** — always return cleanup function for subscriptions:
```tsx
useEffect(() => {
const controller = new AbortController();
fetchTasks(controller.signal).then(setTasks);
return () => controller.abort(); // ✅ Cleanup on unmount
}, []);
```
4. **`useRef` for values that don't trigger re-renders:**
```tsx
// ✅ Timer ref — doesn't cause re-render
const timerRef = useRef>();
useEffect(() => {
timerRef.current = setInterval(pollStatus, 5000);
return () => clearInterval(timerRef.current);
}, []);
```
---
### React 19 Patterns
1. **`use()` hook** — read resources, promises, and context directly in render:
```tsx
// ✅ Read a promise during render (replaces useEffect + useState)
function TaskDetail({ taskPromise }: { taskPromise: Promise }) {
const task = use(taskPromise);
return {task.title}
;
}
// ✅ Read context without useContext
function TaskActions() {
const theme = use(ThemeContext);
return ;
}
```
2. **`useActionState`** for form actions (replaces `useFormState`):
```tsx
// ✅ Server-aware form with pending state
async function createTask(_prev: State, formData: FormData) {
const result = await api.createTask(Object.fromEntries(formData));
return result.error ? { error: result.error } : { success: true };
}
function TaskForm() {
const [state, formAction, isPending] = useActionState(createTask, { error: null });
return (
);
}
```
3. **`useOptimistic`** for instant UI feedback:
```tsx
const [optimisticTasks, addOptimistic] = useOptimistic(
tasks,
(state, newTask: Task) => [...state, newTask],
);
// Call addOptimistic(tempTask) before await api.createTask(tempTask)
```
4. **`