React
React rules and best practices for Claude Code.
20.5k
- React
- Next.js
- TypeScript
- Tailwind
- Redux
- Zustand
- Vite
- Shadcn UI
8 TEMPLATES
Next.js App Router + TypeScript Rules
# CLAUDE.md — Next.js App Router + TypeScript ## Routing & rendering - App Router only. Do not create files under `pages/`. - Server Components by default. Add `'use client'` only when you need state, refs, browser APIs, or event handlers. - Co-locate route segments under `app/`. Route handlers go in `route.ts`, pages in `page.tsx`, layouts in `layout.tsx`. - Use `loading.tsx`, `error.tsx`, and `not-found.tsx` for built-in boundaries — don't roll your own when the file convention exists. - Default to streaming with `<Suspense>` instead of blocking the entire route on slow data. ## Data fetching - Fetch in Server Components with `async/await`. Pass data down as props; don't refetch the same data on the client. - Use Server Actions for mutations. Return typed results — never throw raw errors back to the client. - Cache deliberately: choose between `force-cache`, `no-store`, or `revalidate: N` per `fetch` call. Don't leave it implicit. - Tag cache reads (`next: { tags: ['user-list'] }`) and invalidate them with `revalidateTag` after mutations. ## TypeScript - `strict: true` in `tsconfig.json`. No `any`, no `as` casts unless they're narrowing a `unknown`. - Type the `params` and `searchParams` of every page and layout — these are `Promise`s in current Next. - Validate every untrusted input (form data, search params, route handler bodies) with Zod before using it. ## Styling & UI - Tailwind for layout and spacing. No CSS-in-JS for static styles. - Keep one global stylesheet (`app/globals.css`) — no per-component CSS files. - Images: always use `next/image` with explicit width/height or `fill` + a sized parent. ## SEO - Export `metadata` (or `generateMetadata`) from every page. Set `title`, `description`, and `alternates.canonical`. - Generate sitemaps in `app/sitemap.ts` from your data sources, not by hand. - Use `app/[route]/opengraph-image.tsx` to ship per-page OG images, not a single shared one. ## Don't - Don't use `getServerSideProps`, `getStaticProps`, or `getInitialProps` — they don't exist in App Router. - Don't import server-only modules (`fs`, `db client`, etc.) into client components. - Don't disable TypeScript or ESLint errors to ship. - Don't use `useEffect` to fetch data you could fetch on the server.React + TypeScript + Vite SPA Rules
# CLAUDE.md — React + TypeScript + Vite ## Project structure - `src/components/` — reusable components, server-agnostic - `src/features/<feature>/` — feature-scoped components, hooks, types, services - `src/lib/` — pure utilities, no React imports - `src/hooks/` — shared custom hooks (one hook per file) - `src/app/` — top-level routing and providers - Co-locate tests next to the file (`Component.test.tsx`). ## TypeScript - `strict: true` plus `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes`. - No `any`. Use `unknown` and narrow. - Component props are an `interface`, not a `type` alias, when other components might extend them. - Children: `children: React.ReactNode` for free-form, `children: React.ReactElement` for a single element. ## Components - Function components only. No class components. - Default to named exports. One component per file. The file name matches the component. - Keep components under ~150 lines. Split by responsibility, not by size alone. - Server-state, async work, and side effects belong in hooks — never inline `useEffect` for fetching. ## Hooks - Custom hook names start with `use`. They return either an object (multiple values) or a single value — never both. - Always specify the dep array. ESLint `react-hooks/exhaustive-deps` is `error`, not `warn`. - `useEffect` is a last resort. Prefer derived state, `useMemo`, or moving logic to event handlers. ## State - Local state with `useState`/`useReducer`. - Cross-component state with React Context only when prop-drilling exceeds ~3 levels. - Server state with TanStack Query (don't roll your own cache). - Global UI state (modals, toasts) with Zustand or context — keep stores small and feature-local. ## Suspense & errors - Wrap async boundaries in `<Suspense>` with a real fallback (skeletons match the layout). - Wrap routes in `<ErrorBoundary>`. Each boundary owns a recovery action ("Retry", "Go home"). ## Vite - Pin Vite and `@vitejs/plugin-react` versions. Don't auto-update on every PR. - Use `import.meta.env` (not `process.env`). Prefix public env vars with `VITE_`. - Add `vitest` for tests. Reuse Vite's config — no separate Jest setup. ## Don't - Don't put fetches in `useEffect`. Use TanStack Query or move to a Suspense data layer. - Don't pass JSX as a prop just to defer rendering. Use `<Suspense>`. - Don't disable lint rules to silence "missing dep" warnings — fix the underlying bug. - Don't reach for `useCallback` / `useMemo` until profiling proves they help.Next.js + Server Actions + Shadcn UI
# CLAUDE.md — Next.js + Server Actions + Shadcn UI ## Forms & mutations - Use Server Actions for every form. The client form component imports the action and passes it to `<form action={...}>`. - Validate input inside the action with Zod. Reject invalid input before touching the database. - Return a discriminated `{ ok: true, data } | { ok: false, error }` from every action. The client renders state from this — never throws. - Use the `useActionState` (or `useFormState`) hook to keep server-returned errors next to the form fields. - For optimistic UI, use `useOptimistic` — don't manually duplicate state. ## Shadcn UI - Components live in `components/ui/`. Generated with `npx shadcn add <component>` — keep them under your control. - Compose from primitives: never reach for a "kitchen sink" library on top of Shadcn. - For variants, extend the existing `cva` class string — don't fork the file. - Re-export Shadcn primitives from a single barrel only if you also wrap them. Otherwise import directly from `components/ui/<file>`. - Shadcn components are client components by default. If a primitive is purely presentational, copy it into a server component file and drop the `'use client'` directive. ## Validation - One Zod schema per form, exported from `lib/validators/<feature>.ts`. - Infer types: `type FormInput = z.infer<typeof formSchema>`. Never declare the type by hand. - Reuse the schema on the client (resolver for `react-hook-form`) and the server (action input check). Don't duplicate. ## Patterns - Loading states: use the `pending` flag from `useActionState` — don't track it with `useState`. - After a successful mutation, call `revalidatePath` or `revalidateTag` from the action — not `router.refresh()` from the client. - Handle redirects with `redirect()` *inside* the action. The client never decides where to go after a mutation. ## Don't - Don't wrap a Server Action in a fetch from a client. Pass it to `<form action>` or call it directly via `useTransition`. - Don't return raw `Error` objects — they don't serialize. Return shape-checked plain objects. - Don't put business logic in form components. Keep the form thin; logic lives in the action. - Don't mix Shadcn with another UI kit. Pick one design system and commit.React Hooks Best Practices
# CLAUDE.md — React Hooks Best Practices ## Rules of Hooks - Call hooks at the top level of a function component or another hook. Never inside loops, conditions, or after early returns. - Hook names start with `use`. ESLint enforces this and it's load-bearing for the linter — don't rename around it. ## useState - Initialize with the value, not a getter, unless construction is expensive: `useState(() => expensiveInit())`. - One state per concern. Don't combine unrelated state into one big object. - For derived state, compute during render. Don't sync via `useEffect`. ## useEffect — the last resort - Effects are for synchronizing with external systems (DOM, network, timers). Not for "running code when X changes". - If the body of an effect could be an event handler, it should be. - Always specify the dep array. Empty `[]` only when you truly mean "once" and you're not using any value from the render. - Always return a cleanup function for subscriptions, intervals, observers. - Two effects, two responsibilities — never combine unrelated work into one effect. ## useRef - For values that need to persist across renders without causing re-renders. - For DOM refs: `useRef<HTMLInputElement>(null)`. Type the element narrowly. - Mutating `ref.current` doesn't re-render. If you need a re-render, use state. ## useMemo / useCallback - Default to *not* using them. They have a real cost — memoization itself takes work. - Use them when: - A value is passed to a memoized child and identity matters - A computation is provably expensive (you've measured) - Don't memoize primitive values. Don't memoize unstable inputs. ## Custom hooks - A hook is a function that calls other hooks. Anything that doesn't is a utility — keep it as a plain function. - One hook, one purpose. Compose smaller hooks; don't write a 200-line super-hook. - Return tuples for two values where order is obvious (`[value, setValue]`). Return objects when you have 3+ named values. ## useReducer - Reach for `useReducer` when state transitions are complex or multiple events update overlapping state. - Reducers must be pure. No fetches, no side effects. - Action types are string literals — type them as a discriminated union. ## Don't - Don't use `useEffect` for data fetching when a server framework or query library is available. - Don't read state values inside an effect via stale closures — add them to deps or use a ref. - Don't create new objects/arrays in render and pass them to memoized children. Memoize them. - Don't disable `react-hooks/exhaustive-deps`. Fix the dependency, don't hide the warning.React + TanStack Query
# CLAUDE.md — React + TanStack Query ## Setup - One `QueryClient` per app, instantiated above `<QueryClientProvider>`. - Add `<ReactQueryDevtools>` in dev only — gate with `import.meta.env.DEV` or `process.env.NODE_ENV`. - Default options live on the `QueryClient` constructor: `staleTime`, `retry`, `refetchOnWindowFocus`. Don't repeat them per-hook unless overriding. ## Query keys - Keys are arrays. The first element is a stable string namespace; following elements are scoping params: - `['users']` — the list - `['users', userId]` — one user - `['users', userId, 'posts']` — nested resource - Define key factories per feature in `features/<feature>/queries.ts`: ```ts export const userKeys = { all: ['users'] as const, detail: (id: string) => [...userKeys.all, id] as const, } ``` - Never construct keys inline at call sites — invalidation will silently miss. ## Queries - One custom hook per query: `useUser(id)`, `useUserList()`. The hook owns the query function and key. - Always type the data via the query function's return type — don't type at the hook signature. - Suspense queries (`useSuspenseQuery`) for data that's required to render the page; standard `useQuery` for optional data. - `select` to derive computed values without re-running the query. ## Mutations - One hook per mutation: `useUpdateUser()`. The hook owns the mutation function and post-mutation invalidation. - After a mutation, invalidate the query keys that depend on the changed data — don't refetch a single specific query when a key prefix would do. - For optimistic updates, use `onMutate` with `setQueryData` and roll back in `onError` using the snapshot. ## Caching - `staleTime` defaults to 0 — set per-feature to a value that makes sense (5 minutes for user-list, 0 for live data). - `gcTime` (formerly `cacheTime`) defaults to 5 minutes — increase for data the user revisits. - `refetchOnWindowFocus`: `false` for dashboards where freshness is < 5 min anyway; `true` for actively editable resources. ## Don't - Don't fetch in `useEffect` when TanStack Query exists. The cache, retry, and dedup logic are the point. - Don't share query keys across features. Namespace strictly. - Don't use a query for data that never changes — that's a constant. Import it directly. - Don't disable a query with `enabled: false` and then refetch with `refetch()` — pass the right deps to the key instead. - Don't put `useQuery` inside a conditional. The hook order is the cache identity.React Component Architecture
# CLAUDE.md — React Component Architecture ## Component types - **UI primitives** — `<Button>`, `<Input>`, `<Card>`. Live in `components/ui/`. No app logic, no data fetching, no domain knowledge. - **Feature components** — `<UserProfile>`, `<InvoiceList>`. Live in `features/<feature>/`. Compose primitives, own feature logic. - **Layouts / providers** — `<AppLayout>`, `<ThemeProvider>`. Live in `app/` or `layouts/`. Mostly structural. ## Composition over configuration - Prefer `children` and slot props over a long flat prop list. A component with 12 props is asking to be split. - Compound components for related parts: `<Tabs>`, `<Tabs.List>`, `<Tabs.Trigger>`, `<Tabs.Panel>`. Share state via context internal to the parent. - Render-props or hook patterns when the consumer needs to customize the rendering, not just the data. ## Prop design - Required props first, optional props after. - Boolean props default to `false`. Naming is positive: `disabled`, not `notDisabled`. - Avoid `is*` and `has*` for the same concept. Pick one (`isLoading`, `hasError`). - Use discriminated unions for mutually exclusive prop sets (`{ kind: 'text', value } | { kind: 'icon', icon }`). - Spread the rest of native HTML props (`...rest`) to the underlying element so consumers can pass `aria-*`, `data-*`, etc. ## Folder layout ``` features/users/ components/ UserCard.tsx UserAvatar.tsx hooks/ useUser.ts api.ts types.ts index.ts // public surface ``` - The `index.ts` is the only file other features should import from. - Internal files importing each other use relative paths; cross-feature imports use the alias (`@/features/users`). ## Splitting components - Split when: - The component has more than one reason to change - State and rendering are independent - You'd want to test parts separately - Don't split for line count alone. A 200-line cohesive component beats four 50-line confused ones. ## Performance - Memoize at boundaries (`React.memo`) only after profiling. Most components don't need it. - Big lists: use `react-window` or `@tanstack/react-virtual` instead of rendering thousands of nodes. - Code-split at the route level with `React.lazy`. Don't lazy-load primitives. ## Don't - Don't pass JSX as a prop just to "skip" rendering — use `<Suspense>` or conditional rendering. - Don't barrel-export every component from `components/index.ts`. Big barrels break tree-shaking. - Don't put feature logic into UI primitives. The primitive should work in any app. - Don't reach into a child's internals via refs. If you need to, redesign the API.React + Redux Toolkit
# CLAUDE.md — React + Redux Toolkit ## When to use Redux - Use Redux Toolkit (RTK) when state is shared across many disconnected parts of the app, or when actions cause coordinated changes. - For server cache, use RTK Query (or TanStack Query). Don't keep server data in slices unless you have a specific reason. - For local component state, use `useState`/`useReducer`. Don't promote everything to global. ## Slices - One slice per domain. File: `src/features/<domain>/<domain>Slice.ts`. - Use `createSlice`. Reducers are written as if mutating — Immer handles immutability. - Co-locate selectors in the same file. Export `selectX` functions; never read state shape from components. - Action creators are auto-generated. Don't write them by hand. ## Store - Define the store once in `src/app/store.ts`: ```ts export const store = configureStore({ reducer: { users: usersReducer, ... } }) export type RootState = ReturnType<typeof store.getState> export type AppDispatch = typeof store.dispatch ``` - Wrap `useDispatch` and `useSelector` once with the typed versions: ```ts export const useAppDispatch: () => AppDispatch = useDispatch export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector ``` - Components import the typed hooks, never the raw `useDispatch`. ## Async with createAsyncThunk - Use `createAsyncThunk` only for actions that touch async work *and* need to be tracked in slice state (loading flag, error). - For simple data fetching, prefer RTK Query. - Three reducers per thunk: `pending`, `fulfilled`, `rejected`. Always handle `rejected` — never silently swallow. ## RTK Query - One `api` slice per backend. `createApi({ baseQuery, endpoints })`. - Tag-based invalidation: each query provides tags, each mutation invalidates them. Don't manually `refetch`. - Co-locate endpoints with the feature. Re-export hooks from a feature index file. ## Selectors - Use `createSelector` (Reselect, re-exported by RTK) for derived data. - Inputs are simple state-getter selectors; the result function does the computation. - Don't pass new objects/arrays through `useSelector` without `createSelector` — you'll re-render every state change. ## Don't - Don't mutate state outside slice reducers. Immer only works inside `createSlice`. - Don't dispatch from inside reducers. Reducers are pure. - Don't put non-serializable values (Maps, Sets, class instances, Promises) in state. - Don't store the same data in two slices. Pick the owner; let others read.TypeScript React Component Types
# CLAUDE.md — TypeScript React Component Types ## Prop typing - Props are an `interface`, not a `type`, when other components might extend them. - Required props first. Optional props after. - Document non-obvious props with TSDoc — it shows up in editor hovers. - Avoid optional booleans for two-state things. `kind: "primary" | "secondary"` reads better than `isPrimary?: boolean` when you have more than one variant. ## Common patterns ```ts // Children: free-form type Props = { children: React.ReactNode } // Children: a single element type Props = { children: React.ReactElement } // Render prop type Props = { children: (state: State) => React.ReactNode } // Forwarded native props type Props = React.ComponentPropsWithoutRef<"button"> & { variant: "primary" | "ghost" } ``` - `ComponentPropsWithoutRef<"button">` over `ButtonHTMLAttributes` — picks up `data-*`, `aria-*`, and event handlers automatically. ## Discriminated unions for variants ```ts type Props = | { kind: "icon"; icon: React.ReactNode; label: string } | { kind: "label"; label: string } ``` - `kind` makes the variant explicit. `switch (props.kind)` exhausts cases. - Don't use `XOR` types or "if A is set, B can't be" comments. Encode it in the type. ## Event handlers - Type the event with the React-specific generic: ```ts onClick: (e: React.MouseEvent<HTMLButtonElement>) => void ``` - For form events, narrow to the element you actually use. - Don't type as `(e: any) => void` "for simplicity". The type cost is one extra word. ## Refs & polymorphic components - `forwardRef` for components that wrap a DOM node and need to expose the ref: ```ts const Button = React.forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => ...) ``` - For polymorphic components (`as` prop), the typing is intricate. Use a battle-tested pattern (e.g., from radix-ui or a small util like `react-polymorphic-types`) — don't reinvent. ## Generics in components - Components can be generic for list/select/table primitives: ```ts function Select<T>({ items, value, onChange }: { items: T[]; value: T; onChange: (v: T) => void }) { ... } ``` - Constrain generics with `extends` when you need a property: `<T extends { id: string }>`. ## Default values - Default props in destructuring: `{ variant = "primary" }: Props`. No `defaultProps`. - For boolean props, default to `false` and name positively. ## Don't - Don't type `children` as `JSX.Element` — too restrictive (excludes strings, fragments, null). - Don't use `React.FC` / `React.FunctionComponent`. It implies an unwanted `children` and complicates generics. - Don't pass through every prop as `{...props}` to `<div>`. Whitelist the props that make sense. - Don't widen prop types ("just in case"). The narrower, the better the contract.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.