You've been there: your React app feels sluggish, re-renders are happening more than you'd like, and debugging sessions stretch into the night. You profile with React DevTools, chase down memoization misses, and still wonder — "Why is this component re-rendering on every keystroke?"
The culprit is often not one big mistake, but dozens of small anti-patterns that accumulate over time. Unnecessary useEffect hooks computing derived state. Prop drilling through five levels instead of composition. Missing key props that silently break list reconciliation. Accessibility oversights that hurt real users.
These issues don't scream during development they whisper performance regressions, bugs during refactors, and frustrated users. In large codebases, they compound until your app feels "heavy" for no obvious reason.
Enter React Doctor
A free, open-source CLI tool that acts like a code physician. Run one command, and it scans your entire React project (Next.js, Vite, Remix, whatever), detects framework setup, React version, and compiler config, then flags issues with file-by-file precision. It outputs a 0-100 health score and prioritizes fixes that deliver the biggest wins.
In this post, you'll learn exactly how to use React Doctor, why its warnings matter, and how to fix the most common issues it surfaces. By the end, you'll have a repeatable workflow to keep your React code lean, accessible, and maintainable.
Table of Contents
- What React Doctor Actually Does
- Getting Started: One Command to Rule Them All
- The Most Common Warnings (And Why They Hurt)
- Fixing Unnecessary useEffect: The Silent Performance Killer
- Beyond Hooks: Accessibility, Keys, and Architecture Smells
- Advanced: Integrating React Doctor into Your Workflow
- Common Gotchas When Interpreting Results
- Wrapping Up: Make Code Health a Habi
What React Doctor Actually Does
React Doctor isn't a runtime profiler like React Scan — it's a static analyzer powered by fast Rust based tooling (Oxlint under the hood). It parses your code without running it, applies rules tuned for modern React (hooks, Server Components, server actions), and catches patterns that lead to bugs or slowness.
Getting Started: One Command to Rule Them All
Installation? None. Just npx it:
Bash
npx -y react-doctor@latest .
The . tells it to scan the current directory. In seconds (yes, even on big monorepos), you'll see output like:
Text
React Doctor vX.Y.Z
Scanning project...
Framework: Next.js 15
React: 19.0.0-rc
Compiler: Turbopack
Health Score: 82/100
Critical (1):
- src/app/actions/deleteUser.ts:18 Missing authentication check in server action
Warnings (42):
- src/components/UserProfile.tsx:45 Derived state in useEffect — compute in render
- src/features/TodoList.tsx:22 Using index as key in mapped list
...
Accessibility (8):
- src/components/AnimatedModal.tsx:67 Missing prefers-reduced-motion check
Gotcha!
If you're in a monorepo, cd into the app folder first or use --dir ./apps/web (check docs for flags).
Accessibility Note
Many warnings highlight a11y gaps, like missing reduced-motion handling that improve UX for users with vestibular disorders.
The Most Common Warnings (And Why They Hurt)
React Doctor surfaces patterns you've probably written without thinking:
- Derived state in useEffect— recomputes on every render cycle instead of during render.
- Uncleaned async in effects → race conditions on unmount.
- Index as key → broken animations/reordering in lists.
- Prop drilling → brittle components, hard refactors.
- Server action security → missing auth checks → vulnerability.
- No reduced-motion→ jarring experiences for sensitive users.
Fixing Unnecessary useEffect: The Silent Performance Kille
This is the #1 warning in most codebases. Classic anti-pattern:
tsx
function ExpensiveList({ items }) {
const [sortedItems, setSortedItems] = useState([]);
useEffect(() => {
setSortedItems([...items].sort((a, b) => a.name.localeCompare(b.name)));
}, [items]);
return <ul>{sortedItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
Why it's bad: Every time items changes → effect runs → state update → re-render → potential loop if parent re-renders often.
Fix —** compute during render:**
tsx
function ExpensiveList({ items }) {
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
return <ul>{sortedItems.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}```
**Tip**
`useMemo `is your friend here, but don't overuse it, only when the sort is expensive. React Doctor helps you spot when you need it.
## Beyond Hooks: Accessibility, Keys, and Architecture Smells
For keys in lists:
Bad:
{items.map((item, index) => )}
Good:
{items.map(item => )}
React Doctor flags index-as-key because reordering items causes unnecessary DOM mutations and lost state.
For accessibility animations without reduced-motion checks:
tsx
// Add this wrapper
const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
if (prefersReducedMotion) {
// no animation
} else {
// animate
}
**Wrapping Up: Make Code Health a Habit**
Mastering tools like React Doctor shifts you from reactive firefighting to proactive quality. Your apps become faster, more accessible, and easier to maintain especially as teams grow.
Next steps:
Run `npx react-doctor@latest .` on your current project today.
Tackle the top 5 warnings.
Add it to CI for ongoing health checks.
Top comments (0)