| paths |
|
|---|
- Components: Arrow function components:
const X = () =>. Class components deprecated. - State:
useState(local),useReducer(complex state),useActionState(form actions) - Data fetching:
use()hook for promises (React 19+), custom hooks for data fetching - Composition: Prefer over inheritance, compound patterns (Card.Header, Card.Body)
- Imports: Named imports only -
import { useState, useEffect } from 'react' - Legacy imports: ALWAYS replace
import * as React from 'react'andimport React from 'react'with named imports
- Handler naming:
handleXfor internal event handlers,onXfor callback prop names - Hook naming: Always prefix with
useif calling other hooks - Purpose-specific hooks: Name after purpose (e.g.,
useChatRoom,useAuth), not lifecycle (useMount) - Exported prop interfaces: Name
<ComponentName>Props, NOT a bareProps. A barePropspollutes the importing file's namespace and collides when multiple component prop interfaces are imported. Internal (non-exported) prop interfaces may usePropsfor brevity since they don't cross file boundaries.
// ❌ WRONG - bare Props pollutes importing files
export interface Props {
open: boolean
onClose: () => void
}
// ✅ CORRECT - prefixed with component name
export interface ChangeNameDialogProps {
open: boolean
onClose: () => void
}
// ✅ OK - non-exported interface can stay bare
interface Props {
open: boolean
}
const ChangeNameDialog = ({ open }: Props) => { ... }Use memoization only when there is a measured performance need or a specific technical requirement:
memo(): Wrap with named function expression for DevTools:export const X = memo(function X(...) { })useCallback: Only when passing tomemo()children, used as hook dependencies, or returned from custom hooks. Do NOT wrap handlers passed to plain DOM elements or non-memo components.useMemo: Only for expensive computations (>1ms), stabilizing props formemo()children, or values used as hook dependencies. Do NOT use for trivial calculations.- State updater functions: Use updater form in
useCallback(setItems(prev => [...prev, item])) to avoid including state in dependency arrays - Incomplete memoization chain: If a component is wrapped with
memo(), ALL props passed to it must be stable (memoized or primitive). Memoizing some props but not all silently breaksmemo().
When reviewing code, do not surface a finding (Critical/High/Medium/Nice-to-Have) recommending useMemo or useCallback purely for referential stability of a value or function passed to a child component that is not memo()-wrapped. Such cleanups produce code churn without measurable benefit — the child re-renders either way, the memo's recomputation cost roughly equals the inline expression's, and the surrounding handler/prop chain typically isn't memoized either.
A memoization finding is legitimate ONLY when at least one of these holds:
- The child is already
memo()-wrapped (and the new memo completes the prop-stability chain — all other props must also be stable, otherwisememo()is silently broken anyway). - The value is used as a hook dependency upstream, where identity changes would re-fire the effect/memo unnecessarily.
- The computation is measurably expensive (>1ms — name the measurement or the input scale that justifies the threshold).
Otherwise, mention it in the "Approved" section as a deliberate non-finding ("inline expression is fine here — child is not memoized, no hook dep") rather than as Nice-to-Have. This prevents trivial memoization tickets from being filed as deferred work that the rules above would tell you not to do in the first place.
- Dependencies: Always include all dependencies in
useEffectarrays - Functions in effects: Define functions inside
useEffectto avoiduseCallback - Objects in effects: Create objects inside
useEffectto avoiduseMemo - No dependency suppression: Never suppress
exhaustive-depslinter warnings
- ❌ Class components (use function components)
- ❌ Generic lifecycle hooks (
useMount,useUnmount) - ❌ Higher-order hooks or passing hooks as props
- ❌ Suppressing dependency linter warnings
- ❌ Chaining effects to update interdependent state
- ❌ Creating objects/functions in dependency arrays without memoization
- ❌
forwardRef(deprecated in React 19+, userefprop directly) - ❌ Legacy React imports (
import * as Reactorimport React) - use named imports - ❌ Wrapping every handler with
useCallback— only when child ismemo()or function is a dependency