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 |