Files
gh-djankies-claude-configs-…/skills/following-the-rules-of-hooks/SKILL.md
2025-11-29 18:22:28 +08:00

190 lines
4.9 KiB
Markdown

---
name: following-the-rules-of-hooks
description: Fix React Rules of Hooks violations - conditional calls, hooks in loops/callbacks/classes
version: 1.0.0
---
# Rules of Hooks
React enforces two invariants on Hook usage. Violating these causes state corruption and unpredictable behavior.
## The Rules
1. **Top-level only** - Never call Hooks inside loops, conditions, nested functions, or try/catch/finally
2. **React functions only** - Call Hooks exclusively from function components or custom Hooks
**Why:** Consistent call order across renders; conditional/dynamic invocation breaks state tracking.
## Valid Hook Locations
✅ Top level of function components
✅ Top level of custom Hooks (`use*` functions)
```javascript
function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
return width;
}
```
## Common Violations
| 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 |
## Common Fixes
### Conditional Hooks
**Wrong:**
```javascript
function Profile({ userId }) {
if (userId) {
const user = useUser(userId);
}
}
```
**Right:**
```javascript
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.
### Hooks in Loops
**Wrong:**
```javascript
function List({ items }) {
return items.map(item => {
const [selected, setSelected] = useState(false);
return <Item selected={selected} />;
});
}
```
**Right:**
```javascript
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.
### Hooks in Event Handlers
**Wrong:**
```javascript
function Form() {
function handleSubmit() {
const [loading, setLoading] = useState(false);
setLoading(true);
}
return <button onClick={handleSubmit}>Submit</button>;
}
```
**Right:**
```javascript
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.
### Hooks in Classes
**Wrong:**
```javascript
function BadCounter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
```
**Right:**
```javascript
function Counter() {
const [count, setCount] = useState(0);
return <div>{count}</div>;
}
```
**Pattern:** Use function components for Hooks.
### Hooks in Callbacks
**Wrong:**
```javascript
function Theme() {
const style = useMemo(() => {
const theme = useContext(ThemeContext);
return createStyle(theme);
}, []);
}
```
**Right:**
```javascript
function Theme() {
const theme = useContext(ThemeContext);
const style = useMemo(() => createStyle(theme), [theme]);
}
```
**Pattern:** Call Hook at top level, reference in callback.
### Hooks After Early Returns
**Wrong:**
```javascript
function User({ userId }) {
if (!userId) return null;
const user = useUser(userId);
return <div>{user.name}</div>;
}
```
**Right:**
```javascript
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
Custom Hooks may call other Hooks because they execute during render phase:
```javascript
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.
## Quick Diagnostic
**ESLint error:** "React Hook cannot be called..."
1. Check location: Is Hook inside if/loop/try/handler/class?
2. Move Hook to top level of component/custom Hook
3. Keep conditional logic, move Hook call outside it
4. Use conditional rendering, not conditional Hooks
**Reference:** https://react.dev/reference/rules/rules-of-hooks