Files
everything-claude-code/rules/react/coding-style.md
T
AlexisLeDain 1f0486b8d1 feat(rules): add rules/react/ track
Five rule files mirroring per-language convention (coding-style,
hooks, patterns, security, testing). Each has `paths:` glob
frontmatter for auto-activation when editing matching files.

- coding-style.md: file extensions, naming, JSX, RSC boundary
- hooks.md: React hooks (NOT Claude Code hooks) — rules-of-hooks,
  dep arrays, cleanup, memoization, React 19 additions
- patterns.md: container/presentational split, state location
  decision tree, Suspense + error boundaries, forms, data fetching
- security.md: dangerouslySetInnerHTML, unsafe URL schemes,
  server-action validation, env-var leaks, CSP
- testing.md: RTL queries, userEvent, async, MSW, axe, anti-patterns

Each file extends typescript/* and common/* rules.
2026-05-20 09:52:24 +02:00

4.3 KiB

paths
paths
**/*.tsx
**/*.jsx
**/components/**/*.ts
**/components/**/*.js
**/hooks/**/*.ts
**/hooks/**/*.js

React Coding Style

This file extends typescript/coding-style.md and common/coding-style.md with React specific content.

File Extensions

  • .tsx for any file containing JSX, even one-liner snippets
  • .ts for pure logic, custom hooks without JSX, type definitions, utilities
  • .test.tsx / .test.ts mirroring the source file
  • Use .jsx only when the project intentionally avoids TypeScript — flag every new untyped React file in review

Naming

  • Components: PascalCase for both the symbol and the file (UserCard.tsx, default export UserCard)
  • Custom hooks: useCamelCase for the symbol, kebab-case for the file when the project convention is kebab-case (use-debounce.ts exports useDebounce)
  • Context: <Domain>Context symbol, <Domain>Provider provider component, use<Domain> consumer hook
  • Event handlers: handleClick, handleSubmit inside the component; the prop that receives it is onClick, onSubmit
  • Boolean props: isLoading, hasError, canSubmit — never loading or error alone for booleans

Component Shape

type Props = {
  user: User;
  onSelect: (id: string) => void;
};

export function UserCard({ user, onSelect }: Props) {
  return (
    <button type="button" onClick={() => onSelect(user.id)}>
      {user.name}
    </button>
  );
}
  • Prefer type Props = {} for closed component prop shapes
  • Use interface only when the prop type is extended via declaration merging or exported as a public API extension point
  • Always destructure props in the parameter list — no props.user access inside the body
  • Type the return implicitly through JSX (function Foo(): JSX.Element only when the function returns conditionally and the union confuses inference)

JSX

  • Self-close tags with no children: <img />, <UserCard user={u} />
  • Use fragments <>...</> over wrapper <div> when no DOM element is needed
  • Conditional rendering: {condition && <Foo />} for booleans, ternary for either/or, early return for guard clauses
  • Never put logic inline in JSX when it reads as multi-line — extract to a const above the return or a function
// Prefer
const greeting = user.isAdmin ? "Welcome, admin" : `Hello ${user.name}`;
return <h1>{greeting}</h1>;

// Over
return <h1>{user.isAdmin ? "Welcome, admin" : `Hello ${user.name}`}</h1>;

Server / Client Boundary (Next.js App Router, RSC)

  • Default a new file to Server Component — only add "use client" when the file uses state, effects, refs, browser APIs, or event handlers
  • Place the "use client" directive on line 1, before any imports
  • Never import a Client Component file from inside a "use server" action file
  • Never re-export server-only code through a client module — the bundler will silently include it

Imports

  • React imports first: import { useState } from "react"
  • Then third-party libs, then absolute project imports, then relative
  • Type-only imports: import type { ReactNode } from "react" — never mix runtime and type imports in one statement when ESLint's consistent-type-imports is configured

Hooks Discipline

See hooks.md for the full ruleset. Style highlights:

  • Custom hooks must start with use — enforced by eslint-plugin-react-hooks
  • Group all hook calls at the top of the component, before any conditional logic
  • Avoid creating ad-hoc hooks for one-line wrappers — inline the call instead

State

  • Local first (useState), lift only when shared
  • Context for cross-cutting state read by many components (theme, auth, i18n) — not for high-frequency updates
  • External store (Zustand, Jotai, Redux Toolkit) when state must persist across route changes, sync across tabs, or be debugged via devtools
  • Never duplicate state that can be derived — compute during render

Class Components

Forbidden in new code. Convert legacy class components to function components when touching them for non-trivial changes.

File Layout per Component

components/UserCard/
  UserCard.tsx
  UserCard.module.css   # or styled-components, or Tailwind classes inline
  UserCard.test.tsx
  index.ts              # re-export only

Inline single-file components are fine for trivial presentational pieces.