react-best-practices
SKILL.md
React Best Practices
Component Design
- One component per file. Name the file the same as the component.
- Prefer function components — never write class components.
- Keep components small and focused. If a component exceeds ~150 lines, split it.
- Define components at module scope — never define components inside other components (breaks state preservation and identity).
- Colocate related files (component, hook, types, styles, tests) in the same directory.
Props
- Destructure props in the function signature:
function UserCard({ name, email, avatar }: UserCardProps) { ... }
- Use
childrenviaReact.PropsWithChildrenor explicitchildren: React.ReactNode. - Prefer specific prop types over
Record<string, unknown>orany. - Avoid prop drilling beyond 2-3 levels — use composition, context, or a state management solution.
Hooks
- Follow the Rules of Hooks: only call at the top level, only in React components or custom hooks.
- Extract reusable logic into custom hooks (
use*prefix). - Keep hooks focused — a hook that does too many things should be split.
State Management
- Keep state as local as possible. Lift only when sibling components need it.
- Use
useReducerfor complex state with multiple related transitions. - Reserve context for truly global concerns (theme, auth, locale) — not frequently changing data.
- For server state, use TanStack Query or a similar library instead of
useState+useEffect.
React 19 Hooks
use(): Read context or unwrap promises inside components. Replaces someuseContextpatterns.useActionState(): Manage form submission state (pending, error, result) without manual boilerplate.useOptimistic(): Show optimistic UI immediately during async operations.useTransition(): Wrap non-urgent state updates to keep the UI responsive.
React 19 Patterns
Automatic Memoization (React Compiler)
When using the React Compiler:
- Remove manual
React.memo(),useMemo(), anduseCallback()— the compiler handles memoization automatically. - If not using the compiler, still be intentional — only memoize when profiling reveals a real performance issue.
Actions and Forms
Use React 19 Actions for form handling:
function CreatePost() {
const [state, action, isPending] = useActionState(createPostAction, null);
return (
<form action={action}>
<input name="title" required />
<button type="submit" disabled={isPending}>
Create
</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}
Server Components
- Default to server components for non-interactive content (data display, lists, static layouts).
- Add
"use client"only when the component needs interactivity (event handlers, hooks, browser APIs). - Push
"use client"boundaries as far down the tree as possible.
Performance
- Use
React.lazy()+Suspensefor code-splitting large routes and heavy components. - Use
startTransitionfor expensive state updates that don't need immediate rendering. - Wrap
Suspenseboundaries around async data fetching to avoid waterfall loading. - Avoid creating new objects/arrays in JSX props on every render — extract to constants or memoize.
Patterns
Composition over Configuration
Prefer composable children over giant config props:
// Prefer
<Dialog>
<Dialog.Header>Title</Dialog.Header>
<Dialog.Body>Content</Dialog.Body>
<Dialog.Footer>
<Button>Close</Button>
</Dialog.Footer>
</Dialog>
// Avoid
<Dialog
header="Title"
body="Content"
footer={<Button>Close</Button>}
/>
Render Props & Children as Function
Use when a component needs to share render-time data without prescribing UI:
<DataLoader query={userQuery}>{(data) => <UserProfile user={data} />}</DataLoader>
Custom Hook + Component Pairs
Separate logic from presentation:
function useUserSearch(query: string) {
// filtering, debouncing, API calls
return { results, isLoading, error };
}
function UserSearch() {
const [query, setQuery] = useState("");
const { results, isLoading } = useUserSearch(query);
// render
}
Error Handling
- Use Error Boundaries to catch render errors and show fallback UI.
- Don't use Error Boundaries for event handler errors — use try/catch in the handler.
- Provide meaningful fallback UI, not blank screens.
Testing
- Test behavior, not implementation. Query by role, label, or text — not by test IDs or CSS classes.
- Use
@testing-library/reactfor component tests. - Mock at the network boundary (e.g., MSW) rather than mocking hooks or internal state.
- Test custom hooks with
renderHookfrom@testing-library/react.
Project Structure
src/
├── components/ # shared/reusable components
│ └── button/
│ ├── button.tsx
│ ├── button.test.tsx
│ └── index.ts
├── features/ # feature-specific code (components, hooks, utils)
│ └── auth/
├── hooks/ # shared custom hooks
├── lib/ # utilities, API clients, helpers
├── types/ # shared TypeScript types
└── app/ # routes / pages
Colocate feature-specific code in features/ and only promote to components/ or hooks/ when reused across features.
Weekly Installs
6
Repository
grahamcrackers/skillsFirst Seen
Feb 25, 2026
Security Audits
Installed on
gemini-cli6
github-copilot6
codex6
kimi-cli6
cursor6
opencode6