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.tsx
    • components/complex-component/complex-component-item.tsx
    • components/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 — not ItemContent, CardWrapper.
  • SHOULDPart components should describe their role: FolderActionMenu, DialogHeader, FormFieldError — not just Actions, 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 Button with variant="danger" is better than a new DangerButton.
  • 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 className usage.
  • MUSTUse Button component variants instead of raw <button> with custom styling.
  • MUSTCompose UI from design system primitives; only reach for custom className when design system doesn't cover the case.

Styling Approach

  • MUSTMinimize custom className usage 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 (from nuqs/adapters/next/app). Without it, hooks silently fail.
  • MUSTIn Server Components, use createSearchParamsCache — call parse() at the page level, then get() in nested components.
  • MUSTUse throttleMs on rapid-update inputs (search boxes, sliders) to avoid browser History API rate limiting.
  • SHOULDSet shallow: false when URL state changes should trigger a server re-render.
  • SHOULDUse useState for 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 useEffect unless 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 useActionState for 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"; then React.useCallback(...)
  • SHOULDIf JSX runtime requires it, use import React from "react"; plus named hooks — or import type React when 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-component to root element (kebab-case, matches filename) for DevTools identification.
  • MUSTAdd data-testid to 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 && } renders 0 when count is 0. Use ternary: {count > 0 ? : null}.
  • MUSTUse lazy initializer for expensive useState: useState(() => buildIndex(items)), not useState(buildIndex(items)) — the non-lazy form runs on every render.
  • SHOULDUse startTransition to 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.