React Rules
Rule
Scope: All apps and packages.
Component Design
- MUSTEach component does one thing well (single responsibility). Keep it minimal and "dumb" by default (rendering-focused).
- MUSTWhen logic grows, extract business logic into custom hooks. Components focus on composition and render.
- MUSTAvoid massive JSX blocks. Compose smaller, focused components instead.
- SHOULDColocate code that changes together (component + hook + types in same folder).
- SHOULDOrganize complex components in a folder:
components/complex-component/complex-component-root.tsxcomponents/complex-component/complex-component-item.tsxcomponents/complex-component/index.ts
Component Naming
- MUSTUse specific, descriptive names that convey purpose. Avoid generic suffixes like
-content,-wrapper,-container,-component. - NEVERCreate "god components" with vague names like
PageContent,MainWrapper,ComponentContainer. - NEVERCreate boundary-hack components (for example
DashboardShell,DashboardWrapper,PageContent,ClientLayout) whose primary purpose is hosting"use client"for an entire route. - SHOULDName components by their domain role:
FolderThumbnail,ProductCard,UserAvatar— notItemContent,CardWrapper. - SHOULDPart components should describe their role:
FolderActionMenu,DialogHeader,FormFieldError— not justActions,Header,Error.
Reuse First
LLMs default to creating new components instead of finding existing ones. This rule exists to counteract that bias.
- MUSTBefore creating any component, hook, or utility, search the codebase for existing implementations that serve the same or similar purpose. Use Glob and Grep — not memory.
- MUSTIf a similar component exists, extend it with a variant/prop rather than creating a new one. A
Buttonwithvariant="danger"is better than a newDangerButton. - MUSTIf the same visual pattern appears in 2+ places, extract a shared component immediately. Do not wait — duplication becomes divergence.
- MUSTPlace shared components in a central location (
components/,packages/ui/, or the project's established shared directory), not next to the first consumer. - NEVERCreate a single-use component that duplicates an existing pattern. If it looks like something that already exists, it probably does — search first.
- NEVERFork an existing component into a copy with small modifications. Add a prop/variant to the original.
Design System First
- MUSTCheck for the existence of design system primitives (
Stack,Grid,Container,Text,Heading) in the project before using them. - MUSTIF primitives exist: Use them for layout and typography instead of raw HTML.
- MUSTIF primitives DO NOT exist: Use raw HTML (
div,h1,p) with utility classes. - MUSTWhen missing a primitive and the pattern repeats (or will repeat), define the primitive rather than using one-off
classNameusage. - MUSTUse
Buttoncomponent variants instead of raw<button>with custom styling. - MUSTCompose UI from design system primitives; only reach for custom
classNamewhen design system doesn't cover the case.
Styling Approach
- MUSTMinimize custom
classNameusage in app components; rely on design system component props (if available). - SHOULDUse semantic props (
variant="muted",size="sm") over utility classes. - SHOULDWhen custom classes are needed, keep them minimal and focused on layout/positioning only.
State Management
- MUSTURL-visible state (filters, tabs, pagination, modals) goes in search params via nuqs. URL is the source of truth.
- MUSTWrap the app in
NuqsAdapter(fromnuqs/adapters/next/app). Without it, hooks silently fail. - MUSTIn Server Components, use
createSearchParamsCache— callparse()at the page level, thenget()in nested components. - MUSTUse
throttleMson rapid-update inputs (search boxes, sliders) to avoid browser History API rate limiting. - SHOULDSet
shallow: falsewhen URL state changes should trigger a server re-render. - SHOULDUse
useStatefor strictly local, ephemeral UI state. - SHOULDUse Zustand stores for shared state across the app (or across non-trivial feature boundaries).
- SHOULDFor complex component-internal sharing, provide a local Context or a local Zustand store/provider that is scoped to the component tree.
- MUSTAvoid prop drilling for widely shared state; prefer Context or Zustand.
Props & Types
- MUSTProperly type all props (no
any). Reuse shared types where possible. - SHOULDAvoid passing large or generic objects in props. Prefer clear, specific props (IDs or primitives) and derive the rest inside.
- SHOULDKeep prop surfaces stable to reduce re-renders (prefer primitives/IDs over new object/array instances).
Documentation
- SHOULDInclude minimal JSDoc at the component/hook level to explain intent and any non-obvious behavior.
- SHOULDDocument props that have constraints, side-effects, or require non-obvious usage.
Hooks & Effects
- MUSTPrefer custom hooks for business logic, data fetching, and side-effects.
- MUSTAvoid
useEffectunless synchronizing with an external system. Before writing one, ask: "Does this run because the component was displayed, or because of a user interaction?" If the latter, it belongs in an event handler. - MUSTSee
react-correctness.md§ State & Effects for the full list of effect anti-patterns — derived state, key resets, effect chains, parent notification, store subscriptions, and more. - SHOULDMemoize only when necessary (
useMemo/useCallback), and prefer moving logic into hooks first.
JSX
- NEVERPass children as props. Nest children between opening and closing tags.
- NEVERReassign props in components.
React 19
- MUSTUse ref-as-prop instead of legacy
forwardRef. - MUSTUse
use()hook for reading promises and context. - MUSTUse
<Context value={...}>instead of<Context.Provider value={...}>. - SHOULDUse
useActionStatefor form handling with server actions. - NEVERUse
cloneElement— it silently injects props and breaks with wrappers/fragments. Use context, render props, or explicit composition. - NEVERUse
Children.map/forEach/toArray/count/only— child traversal is fragile. Use explicit props or context.
// ref-as-prop (no forwardRef needed)
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
Imports & Hooks Usage
- MUSTDo not use namespace access for hooks in app code (e.g.,
React.useCallback,React.useMemo,React.useState). Import hooks directly.- Correct:
import { useCallback, useMemo, useState } from "react"; - Avoid:
import * as React from "react";thenReact.useCallback(...)
- Correct:
- SHOULDIf JSX runtime requires it, use
import React from "react";plus named hooks — orimport type Reactwhen only typing is needed.
File & Naming
- SHOULDOne file per component by default; group complex components in a folder.
- SHOULDUse kebab-case filenames,
component-name.tsx.
Component Attributes
- MUSTAdd
data-componentto root element (kebab-case, matches filename) for DevTools identification. - MUSTAdd
data-testidto testable elements for Playwright (see testing.md). - SHOULDPrefix child test IDs with component name:
user-profile-edit,product-card-add-to-cart.
Gotchas
- NEVERUse
&&for conditional rendering with numbers.{count && }renders0when count is 0. Use ternary:{count > 0 ? : null}. - MUSTUse lazy initializer for expensive
useState:useState(() => buildIndex(items)), notuseState(buildIndex(items))— the non-lazy form runs on every render. - SHOULDUse
startTransitionto wrap non-urgent state updates (search results, filtered lists) so they don't block user input.
Utilities, Hooks, Functions
- MUSTKeep utilities, hooks, and general functions single-purpose.
- SHOULDOrganize by responsibility in individual folders where appropriate (e.g.,
hooks/use-thing/,utils/format-price/). - SHOULDCo-locate utilities that are truly component-specific next to the component, otherwise place shared items under a common folder (e.g.,
lib/,hooks/,utils/).
Further Reading
For 57 detailed React/Next.js performance rules with code examples, agents can reference the vercel-react-best-practices skill. For advanced composition patterns (compound components, dependency injection via providers), reference the vercel-composition-patterns skill.
For React-specific performance anti-patterns, agents can reference react-performance.md.
For React correctness rules and common bugs, agents can reference react-correctness.md.