| name | typescript-react-reviewer |
| version | 1.2.0 |
| description | Expert code reviewer for TypeScript + React 19 applications. Use when reviewing React code, identifying anti-patterns, evaluating state management, or assessing code maintainability. Triggers: code review requests, PR reviews, React architecture evaluation, identifying code smells, TypeScript type safety checks, useEffect abuse detection, state management review. |
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.
Atmos Review Session Integration
When the prompt contains a <review-agent-run> block, the session may target either a workspace (isolated git worktree) or a project (the project's main checkout). The reviewer flow is target-agnostic โ it only needs the session, current_revision_guid, and run GUIDs from the block โ but respect the target kind if you need to read repo state (target kind is visible in the output of atmos review session-show --session <session_guid>).
Use the run/session metadata to create one inline comment per concrete finding:
atmos review create-comment \
--session <session_guid> \
--revision <current_revision_guid> \
--file <path> \
--side new \
--start-line <line> \
--end-line <line> \
--title "<short title>" \
--run <run_guid> \
--body-stdin <<'EOF'
Severity: P1
Issue: ...
Suggestion: ...
EOF
Prefer --body-stdin (or --body-file <path>) for multi-line bodies; --body "..." is only for short single-line text. After the review is complete, call:
atmos review set-status --run <run_guid> succeeded --summary-stdin <<'EOF'
<one-paragraph summary>
EOF
If the run cannot be completed, call atmos review set-status --run <run_guid> failed --message "<reason>".
For the full command surface (session discovery, comment reading, run lifecycle, body-input conventions, and workspace vs project semantics), see references/atmos-review-cli.md.
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)
const [firstName, setFirstName] = useState('');
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
const fullName = firstName + ' ' + lastName;
useEffect(() => {
if (product.isInCart) showNotification('Added!');
}, [product]);
function handleAddToCart() {
addToCart(product);
showNotification('Added!');
}
React 19 Hook Mistakes
function Form() {
const { pending } = useFormStatus();
return <form action={submit}><button disabled={pending}>Send</button></form>;
}
function SubmitButton() {
const { pending } = useFormStatus();
return <button type="submit" disabled={pending}>Send</button>;
}
function Form() {
return <form action={submit}><SubmitButton /></form>;
}
function Component() {
const data = use(fetch('/api/data'));
}
function Component({ dataPromise }: { dataPromise: Promise<Data> }) {
const data = use(dataPromise);
}
State Mutation Detection
items.push(newItem);
setItems(items);
arr[i] = newValue;
setArr(arr);
setItems([...items, newItem]);
setArr(arr.map((x, idx) => idx === i ? newValue : x));
TypeScript Red Flags
const data: any = response;
const items = arr[10];
const App: React.FC<Props> = () => {};
const data: ResponseType = response;
const items = arr[10];
const App = ({ prop }: 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
Generate the Report
Write the review report to the specified file path. The report MUST follow this exact structure:
Traceability frontmatter: When this review is run inside an Atmos review session, the prompt will include a ready-to-copy YAML frontmatter block (under the key atmos_review). Write that block verbatim as the very first lines of the report file, before the # TypeScript + React 19 Code Review Report heading. Do not edit, reformat, or omit any field. When there is no session context, omit the frontmatter.
Example of the frontmatter the prompt will supply:
---
atmos_review:
session_guid: "<guid>"
run_guid: "<guid>"
base_revision_guid: "<guid>"
current_revision_guid: "<guid>"
skill_id: "typescript-react-reviewer"
generated_at: "<ISO-8601 UTC>"
---
# TypeScript + React 19 Code Review Report
| Entry | Details |
| :--- | :--- |
| **Date** | YYYY-MM-DD HH:MM |
| **Reviewer** | AI (typescript-react-reviewer) |
| **Scope** | [description of what was reviewed โ git diff / specific files / commit range] |
| **Project Stack** | TypeScript + React 19 (detected frameworks/libraries) |
| **Overall Assessment** | **APPROVE** \| **REQUEST_CHANGES** \| **COMMENT** |
---
## Summary
| Metric | Value |
|--------|-------|
| Files Reviewed | X |
| Lines Changed | +Y / -Z |
| Critical Issues | N |
| High Priority | N |
| Architecture Issues | N |
---
## Findings by Priority
### ๐ซ Critical
> None found. โ
(or list each finding with file, line, impact, and fix)
### โ ๏ธ High Priority
(same format)
### ๐ Architecture/Style
(same format)
---
## Recommended Next Steps
1. Address any critical bugs (P0)
2. Refactor high-priority anti-patterns
3. Consider architectural improvements
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
- atmos-review-cli.md - Shared Atmos review CLI reference (symlinked from
atmos-review-fix)
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
const { data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });
const [todos, setTodos] = useState([]);
useEffect(() => setTodos(data), [data]);
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 |