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

4.9 KiB

name, description, version
name description version
following-the-rules-of-hooks Fix React Rules of Hooks violations - conditional calls, hooks in loops/callbacks/classes 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)

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:

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.

Hooks in Loops

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.

Hooks in Event Handlers

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.

Hooks in Classes

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.

Hooks in Callbacks

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.

Hooks After Early Returns

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

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.

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