# Pre-Commit Review Reference (React/TypeScript) ## Review Philosophy **Advisory, not blocking**: Inform decisions, don't prevent commits. **Debt-based categories**: Focus on future maintainability cost. **Context-aware**: Review changes plus broader file context. ## Complete Review Checklist ### 1. Architecture Patterns #### ✅ Feature-Based Structure ``` src/features/auth/ ├── components/ # Auth UI components ├── hooks/ # Auth custom hooks ├── context/ # Auth context ├── types.ts # Auth types └── index.ts # Public API ``` #### 🔴 Technical Layer Structure (Design Debt) ``` src/ ├── components/auth.tsx ├── hooks/auth.ts ├── contexts/auth.tsx └── types/auth.ts ``` **Impact**: Features spread across directories, hard to find all related code. ### 2. Primitive Obsession #### String Primitives **🔴 Design Debt**: ```typescript interface User { id: string // Empty? Invalid format? email: string // Validated? Format? phone: string // Format? Country code? } function getUser(id: string): User // Any string accepted ``` **✅ Better**: ```typescript // Branded types type UserId = Brand type Email = Brand // Or Zod schemas const UserIdSchema = z.string().uuid() const EmailSchema = z.string().email() type UserId = z.infer type Email = z.infer interface User { id: UserId email: Email phone: PhoneNumber } function getUser(id: UserId): User // Only valid IDs accepted ``` #### Number Primitives **🔴 Design Debt**: ```typescript interface Product { price: number // Negative? Too large? quantity: number // Negative? Zero? rating: number // Range? Decimal places? } ``` **✅ Better**: ```typescript const PriceSchema = z.number().positive().max(1000000) const QuantitySchema = z.number().int().nonnegative() const RatingSchema = z.number().min(0).max(5) type Price = z.infer type Quantity = z.infer type Rating = z.infer ``` #### Boolean State Machines **🟡 Readability Debt**: ```typescript const [isLoading, setIsLoading] = useState(false) const [isSuccess, setIsSuccess] = useState(false) const [isError, setIsError] = useState(false) // Can have invalid states: isLoading && isSuccess ``` **✅ Better**: Discriminated union ```typescript type State = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error } // Impossible states are impossible ``` ### 3. Component Design #### Prop Drilling **🔴 Design Debt**: State passed through 3+ levels ```typescript ``` **✅ Fix options**: 1. Context for truly global state 2. Composition to avoid passing props 3. Accept prop drilling for 1-2 levels (it's fine!) #### Mixed Concerns **🟡 Readability Debt**: UI + business logic mixed ```typescript function UserProfile() { // 50 lines of data fetching // 30 lines of validation // 40 lines of state management // 80 lines of JSX // Total: 200 lines, hard to test } ``` **✅ Better**: Separated concerns ```typescript // testable business logic function useUserProfile(userId) { // fetch, validate, manage state return { user, isLoading, error, actions } } // testable UI function UserProfile({ userId }) { const { user, isLoading, error, actions } = useUserProfile(userId) if (isLoading) return if (error) return return } ``` #### God Components **🔴 Design Debt**: Component doing too much (>200 lines) **Signs**: - Multiple state variables (5+) - Many useEffect hooks - Complex conditional rendering - Mixed abstraction levels **Fix**: Extract components and hooks ### 4. Hook Design #### Hook Extraction **🟡 Readability Debt**: Logic that should be a hook ```typescript function Component() { // 50 lines of reusable logic // directly in component } ``` **✅ Better**: ```typescript function useFeature() { // extracted, testable, reusable } function Component() { const feature = useFeature() return } ``` #### Hook Dependencies **🟡 Readability Debt**: Complex dependencies ```typescript useEffect(() => { fetchData(id, filters.category, filters.price, sort, page) }, [id, filters, filters.category, filters.price, sort, page]) // Redundant, complex ``` **✅ Better**: ```typescript const params = useMemo( () => ({ id, category: filters.category, price: filters.price, sort, page }), [id, filters.category, filters.price, sort, page] ) useEffect(() => { fetchData(params) }, [params]) // Or extract to custom hook function useData(id, filters, sort, page) { useEffect(() => { fetchData(id, filters, sort, page) }, [id, filters, sort, page]) } ``` ### 5. Accessibility Review #### Semantic HTML | ❌ Don't Use | ✅ Use Instead | Why | |-------------|--------------|-----| | `
` | ` ``` **If div required**: ```typescript
{ if (e.key === 'Enter' || e.key === ' ') { handleClick() } }} > Click me
``` #### Images and Media **🔴 Design Debt**: Missing alt text ```typescript ``` **🟢 Polish**: Generic alt text ```typescript avatar ``` **✅ Better**: Descriptive alt text ```typescript John Doe's profile picture ``` **Decorative images**: ```typescript ``` #### Dynamic Content **🟢 Polish**: Loading without announcement ```typescript {isLoading && } ``` **✅ Better**: Announced loading ```typescript {isLoading && (
Loading user data...
)} ``` **Error announcements**: ```typescript {error && (
{error.message}
)} ``` #### Modal Accessibility **🔴 Design Debt**: Basic modal ```typescript {isOpen && (

Title

Content

)} ``` **✅ Better**: Accessible modal ```typescript {isOpen && (
e.key === 'Escape' && onClose()} >

Content

)} ``` **With focus management**: ```typescript function Modal({ isOpen, onClose, children }) { const modalRef = useRef(null) useEffect(() => { if (isOpen && modalRef.current) { const previousActiveElement = document.activeElement modalRef.current.focus() return () => { previousActiveElement?.focus() } } }, [isOpen]) // ... rest of modal } ``` #### Color and Contrast **🟡 Readability Debt**: Color-only indicators ```typescript Error Success ``` **✅ Better**: Multiple indicators ```typescript ``` **With ARIA**: ```typescript ``` ### 6. TypeScript Usage #### Type Safety **🔴 Design Debt**: Using `any` ```typescript function processData(data: any) { // No type safety } ``` **✅ Better**: Proper types or `unknown` with validation ```typescript const DataSchema = z.object({ id: z.string(), name: z.string() }) function processData(data: unknown) { const validated = DataSchema.parse(data) // Throws on invalid // validated is now typed } ``` #### Props Interfaces **🟡 Readability Debt**: Inline props type ```typescript function Button(props: { label: string onClick: () => void variant?: 'primary' | 'secondary' }) { // ... } ``` **✅ Better**: Named interface ```typescript interface ButtonProps { label: string onClick: () => void variant?: 'primary' | 'secondary' } function Button({ label, onClick, variant = 'primary' }: ButtonProps) { // ... } ``` #### Discriminated Unions **🟡 Readability Debt**: Multiple booleans for state ```typescript interface State { isLoading: boolean isSuccess: boolean isError: boolean data?: Data error?: Error } ``` **✅ Better**: Discriminated union ```typescript type State = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: Data } | { status: 'error'; error: Error } // Type narrowing works automatically if (state.status === 'success') { state.data // Available, typed correctly } ``` ### 7. Testing Implications #### Testability **🔴 Design Debt**: Untestable component ```typescript function Component() { // 200 lines of tightly coupled logic // Can't test without implementation details } ``` **✅ Better**: Separated, testable ```typescript // testable hook function useLogic() { return { state, actions } } // testable component function Component() { const { state, actions } = useLogic() return } ``` ### 8. Error Handling #### Missing Error Boundaries **🔴 Design Debt**: No error handling ```typescript function AsyncComponent() { const data = useAsyncData() // Can throw return } ``` **✅ Better**: Error boundary wrapper ```typescript }> ``` **Or component-level handling**: ```typescript function AsyncComponent() { const { data, error, isLoading } = useAsyncData() if (isLoading) return if (error) return if (!data) return return } ``` ## Review Priority ### Must Review (🔴 Design Debt) 1. Primitive obsession 2. Prop drilling (3+ levels) 3. Missing error boundaries 4. Non-semantic interactive elements 5. Missing form labels 6. Using `any` without validation ### Should Review (🟡 Readability Debt) 1. Mixed abstractions 2. Complex conditions 3. God components (>200 lines) 4. Missing hook extraction 5. Inline complex logic ### Nice to Review (🟢 Polish) 1. Missing JSDoc 2. Accessibility enhancements 3. Type improvements 4. Performance optimizations ## Advisory Stance **Remember**: This is advisory, not blocking. **User decides**: - Accept debt (with awareness) - Fix critical (design debt) - Fix all - Expand scope **Always acknowledge**: - Time constraints are real - Team decisions are valid - Consistency matters - Sometimes "good enough" is right choice **Provide options, not mandates**.