Fix React Rules of Hooks violations - conditional calls, hooks in loops/callbacks/classes
Detects and fixes React Rules of Hooks violations like conditional calls, hooks in loops, or in event handlers. Automatically restructures code to move hooks to the top level while preserving the intended logic.
/plugin marketplace add djankies/claude-configs/plugin install react-19@claude-configsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
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..."