From react-19
Fix React Rules of Hooks violations - conditional calls, hooks in loops/callbacks/classes
npx claudepluginhub djankies/claude-configs --plugin react-19This skill uses the workspace's default tool permissions.
React enforces two invariants on Hook usage. Violating these causes state corruption and unpredictable behavior.
Reviews React hooks against design principles: object returns, SSR-safe init, effect cleanup, TypeScript generics, performance, and state patterns.
Audits React codebases for anti-patterns including useEffect misuse, stale closures, derived state in effects, and memory leaks. Produces scored gap analysis table with severity ratings and applies prioritized fixes on request.
Provides React best practices for function components, props interfaces, compound components, useState, useEffect hooks, and state management. Useful for optimizing React code architecture and performance.
Share bugs, ideas, or general feedback.
React enforces two invariants on Hook usage. Violating these causes state corruption and unpredictable behavior.
Why: Consistent call order across renders; conditional/dynamic invocation breaks state tracking.
✅ Top level of function components
✅ Top level of custom Hooks (use* functions)
function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
return width;
}
| Violation | Why Invalid | Fix |
|---|---|---|
| Inside if/else | Skipped on some renders | Move to top; use conditional rendering |
| Inside loops | Variable call count | Move to top; manage array state |
| After early return | Unreachable on some paths | Move Hook before return |
| In event handlers | Called outside render | Move to top; use state from closure |
| In class components | Classes don't support Hooks | Convert to function component |
| Inside callbacks | Nested function context | Move Hook to top level |
❌ Wrong:
function Profile({ userId }) {
if (userId) {
const user = useUser(userId);
}
}
✅ Right:
function Profile({ userId }) {
const user = useUser(userId);
if (!userId) return null;
return <div>{user.name}</div>;
}
Pattern: Always call Hook, use conditional rendering for output.
❌ Wrong:
function List({ items }) {
return items.map(item => {
const [selected, setSelected] = useState(false);
return <Item selected={selected} />;
});
}
✅ Right:
function List({ items }) {
const [selected, setSelected] = useState({});
return items.map(item => (
<Item
key={item.id}
selected={selected[item.id]}
onToggle={() => setSelected(s => ({...s, [item.id]: !s[item.id]}))}
/>
));
}
Pattern: Single Hook managing collection, not per-item Hooks.
❌ Wrong:
function Form() {
function handleSubmit() {
const [loading, setLoading] = useState(false);
setLoading(true);
}
return <button onClick={handleSubmit}>Submit</button>;
}
✅ Right:
function Form() {
const [loading, setLoading] = useState(false);
function handleSubmit() {
setLoading(true);
}
return <button onClick={handleSubmit} disabled={loading}>Submit</button>;
}
Pattern: Hook at component level, setter in handler.
❌ Wrong:
function BadCounter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
✅ Right:
function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
Pattern: Use function components for Hooks.
❌ Wrong:
function Theme() {
const style = useMemo(() => {
const theme = useContext(ThemeContext);
return createStyle(theme);
}, []);
}
✅ Right:
function Theme() {
const theme = useContext(ThemeContext);
const style = useMemo(() => createStyle(theme), [theme]);
}
Pattern: Call Hook at top level, reference in callback.
❌ Wrong:
function User({ userId }) {
if (!userId) return null;
const user = useUser(userId);
return <div>{user.name}</div>;
}
✅ Right:
function User({ userId }) {
const user = useUser(userId || null);
if (!userId) return null;
return <div>{user.name}</div>;
}
Pattern: Call all Hooks before any returns.
Custom Hooks may call other Hooks because they execute during render phase:
function useDebounce(value, delay) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebounced(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debounced;
}
Requirements: Name starts with use; called from function component or another custom Hook; follows same Rules of Hooks.
ESLint error: "React Hook cannot be called..."