typescript-react-patterns
install
source · Clone the upstream repo
git clone https://github.com/leejpsd/typescript-react-patterns
Claude Code · Install into ~/.claude/skills/
git clone --depth=1 https://github.com/leejpsd/typescript-react-patterns ~/.claude/skills/leejpsd-typescript-react-patterns-typescript-react-patterns
manifest:
SKILL.mdsource content
TypeScript for React & Next.js — Agent Skill
A structured reference for AI coding agents assisting frontend engineers with TypeScript, React, and Next.js in production environments.
Agent Behavior Rules
Before answering, always verify:
- Server or client? Server Components, Server Actions, and Route Handlers have different type constraints than
components."use client" - Runtime validation needed? Static types do NOT validate API responses, URL params, form data, or localStorage. Data crossing a trust boundary requires Zod or equivalent.
- App Router or Pages Router? Patterns differ significantly. If unclear, ask.
- TypeScript version?
requires 5.0+. Check before suggesting version-dependent features.satisfies - Next.js version?
is aparams
in 15+. Caching model changed in 16+.Promise
Assumptions the agent must NOT make:
- That API responses match their TypeScript types at runtime
- That
values are the expected type (they are alwayssearchParams
)string | string[] | undefined - That
in existing code is intentionalany - That a type assertion (
) is justified without checking contextas - That server-only imports are safe in client components
- That
dependencies in existing code are correctuseEffect
When uncertain:
- State tradeoffs explicitly rather than picking one approach silently
- Mark unstable or version-dependent patterns as such
- Distinguish: [HARD RULE] (violating causes bugs) / [DEFAULT] (override with reason) / [SITUATIONAL] (depends on context)
Decision Guide
Quick: What pattern should I use?
| Situation | Start here |
|---|---|
| Typing component Props, children, events, refs | → |
Narrowing unions, , type guards, utility types | → |
| Next.js params, searchParams, Server Actions, RSC boundary | → |
| Discriminated unions, conditional props, compound components | → |
| API responses, fetch typing, TanStack Query, caching | → |
| Form state, validation, controlled vs uncontrolled | → |
| Local state vs context vs server state vs Zustand | → |
| Re-renders, memoization, accessibility | → |
| Type errors, hydration, stale state, effect bugs | → (hub) + |
| PR review, risk vs preference, architecture smells | → |
| Common mistakes, cargo-cult patterns | → |
Flowchart: Is this data safe to use?
Data comes from... ├─ Inside the app (useState, useReducer, computed) │ → Static typing is sufficient. No runtime validation needed. │ ├─ Outside the app (API, URL, FormData, localStorage, postMessage) │ → [HARD RULE] Validate at runtime. Use Zod or equivalent. │ │ │ ├─ API response → schema.parse(await res.json()) │ ├─ URL params → schema.parse(searchParams) │ ├─ FormData → schema.safeParse({ field: formData.get('field') }) │ ├─ localStorage → schema.safeParse(JSON.parse(stored)) │ └─ postMessage → schema.safeParse(event.data) │ └─ Third-party library callback → Check library types. Add runtime guard if types seem wrong.
Flowchart: Where should this state live?
Is this data from a server/API? ├─ Yes → TanStack Query (NOT useState). See data-fetching-and-api-types.md │ └─ No → Is it shareable via URL? (filters, page, sort) ├─ Yes → searchParams or nuqs. See state-management.md │ └─ No → How many components need it? ├─ 1 component → useState or useReducer ├─ 2-3 in same tree → Lift state up (props) └─ Many across trees → How often does it change? ├─ Rarely (theme, locale, auth) → Context └─ Often (cart, notifications) → Zustand with selectors
Flowchart: Should I memoize this?
Is there a measured performance problem? ├─ No → Don't memoize. Stop here. │ └─ Yes → Can you restructure instead? ├─ Yes → Move state down, extract components. See performance-and-accessibility.md │ └─ No → What needs memoizing? ├─ Expensive computation → useMemo (verify it's truly expensive) ├─ Callback to memoized child → useCallback └─ Component in a long list → React.memo (verify props are stable)
Quick: hard rule vs default vs situational
| Label | Meaning | Example |
|---|---|---|
| [HARD RULE] | Violating causes bugs or security issues. No exceptions. | "Validate API responses at runtime" |
| [DEFAULT] | Recommended unless you have a documented reason to deviate. | "Use for Props" |
| [SITUATIONAL] | Depends on context. Both options are valid. Explain your choice. | "Polymorphic components — only for design-system foundations" |
Code Generation Checklist
Before generating TypeScript/React/Next.js code:
Context
- Confirmed: server or client code?
- Confirmed: App Router or Pages Router?
- Confirmed: TypeScript strict mode enabled?
Type Safety
- No
— useany
with validation or proper typesunknown - No
without documented justificationas - External data (API, URL, form, storage) validated at runtime
- Props use
, only truly optional fields haveinterface?
React
-
typed aschildrenReact.ReactNode - Event handler Props expose values, not event objects
- Effects have stable dependencies and cleanup functions
-
only where needed, as deep as possible"use client" - No server data duplicated into
useState
Next.js (15+)
-
andparams
awaitedsearchParams - Server Actions validate FormData with Zod
- Sensitive code protected with
import 'server-only' - Cross-boundary Props are JSON-serializable (no functions, Dates, Maps)
Accessibility
- Form inputs have associated labels
- Error messages use
role="alert" - Interactive elements are keyboard-accessible
Code Review Checklist
Flag as risk (likely bug or maintenance problem)
without documented reasonany
on external data without validationas
non-null assertion without prior guard!
with object/array dependencies (likely unstable)useEffect- Missing
cleanupuseEffect - Server data copied into
useState
at page/layout level"use client"- Functions passed across server/client boundary
/params
not awaited (Next.js 15+)searchParams- Server Action without FormData validation
Flag as preference (mention, don't block)
vstype
for object shapesinterface- Handler naming convention
- File/folder organization style
- Import ordering
File Index
| File | Scope |
|---|---|
| Narrowing, unions, generics, utility types, inference, vs , , |
| Props, children, events, hooks, context, forwardRef |
| App Router types, params, searchParams, Server Actions, RSC boundaries, metadata |
| Discriminated union Props, compound components, controlled/uncontrolled, polymorphic |
| Fetch typing, Zod validation, TanStack Query, safe response handling |
| Form state, Zod, react-hook-form, Server Actions, progressive enhancement |
| Local state, Context, Zustand, TanStack Query, URL state, decision matrix |
| Memoization tradeoffs, effect stability, semantic HTML, ARIA patterns |
| Quick diagnosis router, serialization issues, null access, re-render errors |
| Risk vs preference, architecture smells, review comment templates |
| 12 common mistakes with root causes and fixes |
playbooks/
— Step-by-step debugging guides (consult when diagnosing specific bugs)
playbooks/| File | Scope |
|---|---|
| Systematic type error resolution with React/Next.js-specific errors |
| SSR/CSR mismatch diagnosis flowchart and fix patterns |
| Infinite loops, stale closures, missing cleanups, real-world debounce example |