SKILL.md
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