rules/react/coding-style.md
This file extends typescript/coding-style.md and common/coding-style.md with React specific content.
.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.jsx only when the project intentionally avoids TypeScript — flag every new untyped React file in reviewPascalCase for both the symbol and the file (UserCard.tsx, default export UserCard)useCamelCase for the symbol, kebab-case for the file when the project convention is kebab-case (use-debounce.ts exports useDebounce)<Domain>Context symbol, <Domain>Provider provider component, use<Domain> consumer hookhandleClick, handleSubmit inside the component; the prop that receives it is onClick, onSubmitisLoading, hasError, canSubmit — never loading or error alone for booleanstype Props = {
user: User;
onSelect: (id: string) => void;
};
export function UserCard({ user, onSelect }: Props) {
return (
<button type="button" onClick={() => onSelect(user.id)}>
{user.name}
</button>
);
}
type Props = {} for closed component prop shapesinterface only when the prop type is extended via declaration merging or exported as a public API extension pointprops.user access inside the bodyfunction Foo(): JSX.Element only when the function returns conditionally and the union confuses inference)<UserCard user={u} /><>...</> over wrapper <div> when no DOM element is needed{condition && <Foo />} for booleans, ternary for either/or, early return for guard clauses// 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>;
"use client" when the file uses state, effects, refs, browser APIs, or event handlers"use client" directive on line 1, before any imports"use server" action fileimport { useState } from "react"import type { ReactNode } from "react" — never mix runtime and type imports in one statement when ESLint's consistent-type-imports is configuredSee hooks.md for the full ruleset. Style highlights:
use — enforced by eslint-plugin-react-hooksuseState), lift only when sharedForbidden in new code. Convert legacy class components to function components when touching them for non-trivial changes.
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.