| name | react-doctor |
| description | Diagnose and fix React component health issues — unnecessary re-renders, missing keys, stale closures, useEffect misuse, memory leaks, bundle size regressions, and dangerouslySetInnerHTML XSS risks. Use when asked to "React performance audit", "why does this re-render", "React health check", "stale closure", "missing dependency array", "React memory leak", "React XSS", "fix useEffect", "component re-renders too often", "React bundle bloat", or "React anti-patterns". Do NOT use for: full E2E testing — see e2e-testing. Do NOT use for: API design — see api-design.
|
| origin | adapted:MIT © millionco |
| license | MIT © 2026 Vũ Văn Tâm |
| version | 1.0.0 |
| compatibility | React ≥ 18. react-scan, why-did-you-render for diagnosis. |
When to Use
- Use when: a component re-renders on every keystroke despite no prop change
- Use when:
useEffect has missing deps causing stale data or infinite loops
- Use when: memory usage grows on route changes (leak in effect cleanup)
- Use when:
dangerouslySetInnerHTML is used anywhere (XSS check needed)
- Do NOT use for: test coverage — see e2e-testing or tdd-workflow
- Do NOT use for: styling issues — see baseline-ui
Unnecessary Re-Renders
<Button style={{ color: 'red' }} onClick={() => handleClick(id)} />
const style = useMemo(() => ({ color: 'red' }), []);
const handleClickStable = useCallback(() => handleClick(id), [id]);
<Button style={style} onClick={handleClickStable} />
function Parent() {
const [count, setCount] = useState(0);
return <ExpensiveChild data={data} />;
}
const ExpensiveChild = memo(({ data }) => { });
useEffect Anti-Patterns
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, []);
useEffect(() => {
const id = setInterval(() => console.log(count), 1000);
return () => clearInterval(id);
}, [count]);
useEffect(() => {
fetch('/api/data').then(r => r.json()).then(setData);
}, [id]);
useEffect(() => {
const controller = new AbortController();
fetch(`/api/data/${id}`, { signal: controller.signal })
.then(r => r.())
.(setData)
.( { (err. !== ) err; });
controller.();
}, [id]);
( {
();
}, [firstName, lastName]);
fullName = ;
Memory Leaks
useEffect(() => {
someAsyncFn().then(data => setData(data));
}, []);
useEffect(() => {
let cancelled = false;
someAsyncFn().then(data => { if (!cancelled) setData(data); });
return () => { cancelled = true; };
}, []);
useEffect(() => {
window.addEventListener('resize', handler);
}, []);
useEffect(() => {
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
Key Mistakes
items.map((item, i) => <Row key={i} item={item} />)
items.map(item => <Row key={item.id} item={item} />)
items.map(item => <Row item={item} />)
XSS — dangerouslySetInnerHTML
<div dangerouslySetInnerHTML={{ __html: userComment }} />
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userComment) }} />
import ReactMarkdown from 'react-markdown';
<ReactMarkdown>{userComment}</ReactMarkdown>
<a href={userInput}>Link</a>
const safeHref = /^https?:\/\//.test(url) ? url : '#';
<a href={safeHref}>Link</a>
Bundle Size
npm install @next/bundle-analyzer
import { FiUser, FiSettings } from 'react-icons/fi';
import FiUser from 'react-icons/fi/FiUser';
import FiSettings from 'react-icons/fi/FiSettings';
Anti-Fake-Pass Rules
Before claiming React health is fixed, you MUST show:
Reference: gates/anti-fake-pass-gate.md