TypeScript + React 19 Code Review Expert
Expert code reviewer with deep knowledge of React 19's new features, TypeScript best practices, state management patterns, and common anti-patterns.
Review Priority Levels
π« Critical (Block Merge)
These issues cause bugs, memory leaks, or architectural problems:
| Issue | Why It's Critical |
|-------|-------------------|
| useEffect for derived state | Extra render cycle, sync bugs |
| Missing cleanup in useEffect | Memory leaks |
| Direct state mutation (.push(), .splice()) | Silent update failures |
| Conditional hook calls | Breaks Rules of Hooks |
| key={index} in dynamic lists | State corruption on reorder |
| any type without justification | Type safety bypass |
| useFormStatus in same component as <form> | Always returns false (React 19 bug) |
| Promise created inside render with use() | Infinite loop |
β οΈ High Priority
| Issue | Impact |
|-------|--------|
| Incomplete dependency arrays | Stale closures, missing updates |
| Props typed as any | Runtime errors |
| Unjustified useMemo/useCallback | Unnecessary complexity |
| Missing Error Boundaries | Poor error UX |
| Controlled input initialized with undefined | React warning |
π Architecture/Style
| Issue | Recommendation |
|-------|----------------|
| Component > 300 lines | Split into smaller components |
| Prop drilling > 2-3 levels | Use composition or context |
| State far from usage | Colocate state |
| Custom hooks without use prefix | Follow naming convention |
Quick Detection Patterns
useEffect Abuse (Most Common Anti-Pattern)
// β WRONG: Derived state in useEffect
const [firstName, setFirstName] = useState('');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// β
CORRECT: Compute during render
const fullName = firstName + ' ' + lastName;
// β WRONG: Event logic in useEffect
useEffect(() => {
if (product.isInCart) showNotification('Added!');
}, [product]);
// β
CORRECT: Logic in event handler
function handleAddToCart() {
addToCart(product);
showNotification('Added!');
}
React 19 Hook Mistakes
// β WRONG: useFormStatus in form component (always returns false)
function Form() {
const { pending } = useFormStatus();
return <form action={submit}><button disabled={pending}>Send</button></form>;
}
// β
CORRECT: useFormStatus in child component
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>Send</button>;
}
function Form() {
return <form action={submit}><SubmitButton /></form>;
}
// β WRONG: Promise created in render (infinite loop)
function Component() {
const data = use(fetch('/api/data')); // New promise every render!
}
// β
CORRECT: Promise from props or state
function Component({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise);
}
State Mutation Detection
// β WRONG: Mutations (no re-render)
items.push(newItem);
setItems(items);
arr[i] = newValue;
setArr(arr);
// β
CORRECT: Immutable updates
setItems([...items, newItem]);
setArr(arr.map((x, idx) => idx === i ? newValue : x));
TypeScript Red Flags
// β Red flags to catch
const data: any = response; // Unsafe any
const items = arr[10]; // Missing undefined check
const App: React.FC<Props> = () => {}; // Discouraged pattern
// β
Preferred patterns
const data: ResponseType = response;
const items = arr[10]; // with noUncheckedIndexedAccess
const App = ({ prop }: Props) => {}; // Explicit props
Review Workflow
- Scan for critical issues first - Check for the patterns in "Critical (Block Merge)" section
- Check React 19 usage - See react19-patterns.md for new API patterns
- Evaluate state management - Is state colocated? Server state vs client state separation?
- Assess TypeScript safety - Generic components, discriminated unions, strict config
- Review for maintainability - Component size, hook design, folder structure
Reference Documents
For detailed patterns and examples:
- react19-patterns.md - React 19 new hooks (useActionState, useOptimistic, use), Server/Client Component boundaries
- antipatterns.md - Comprehensive anti-pattern catalog with fixes
- checklist.md - Full code review checklist for thorough reviews
State Management Quick Guide
| Data Type | Solution | |-----------|----------| | Server/async data | TanStack Query (never copy to local state) | | Simple global UI state | Zustand (~1KB, no Provider) | | Fine-grained derived state | Jotai (~2.4KB) | | Component-local state | useState/useReducer | | Form state | React 19 useActionState |
TanStack Query Anti-Pattern
// β NEVER copy server data to local state
const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
const [todos, setTodos] = useState([]);
useEffect(() => setTodos(data), [data]);
// β
Query IS the source of truth
const { data: todos } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
TypeScript Config Recommendations
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"exactOptionalPropertyTypes": true
}
}
noUncheckedIndexedAccess is critical - it catches arr[i] returning undefined.
Immediate Red Flags
When reviewing, flag these immediately:
| Pattern | Problem | Fix |
|---------|---------|-----|
| eslint-disable react-hooks/exhaustive-deps | Hides stale closure bugs | Refactor logic |
| Component defined inside component | Remounts every render | Move outside |
| useState(undefined) for inputs | Uncontrolled warning | Use empty string |
| React.FC with generics | Generic inference breaks | Use explicit props |
| Barrel files (index.ts) in app code | Bundle bloat, circular deps | Direct imports |