Skill

following-the-rules-of-hooks

Fix React Rules of Hooks violations - conditional calls, hooks in loops/callbacks/classes

From react-19
Install
1
Run in your terminal
$
npx claudepluginhub djankies/claude-configs --plugin react-19
Tool Access

This skill uses the workspace's default tool permissions.

Skill Content

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

ViolationWhy InvalidFix
Inside if/elseSkipped on some rendersMove to top; use conditional rendering
Inside loopsVariable call countMove to top; manage array state
After early returnUnreachable on some pathsMove Hook before return
In event handlersCalled outside renderMove to top; use state from closure
In class componentsClasses don't support HooksConvert to function component
Inside callbacksNested function contextMove 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

Similar Skills
cache-components

Expert guidance for Next.js Cache Components and Partial Prerendering (PPR). **PROACTIVE ACTIVATION**: Use this skill automatically when working in Next.js projects that have `cacheComponents: true` in their next.config.ts/next.config.js. When this config is detected, proactively apply Cache Components patterns and best practices to all React Server Component implementations. **DETECTION**: At the start of a session in a Next.js project, check for `cacheComponents: true` in next.config. If enabled, this skill's patterns should guide all component authoring, data fetching, and caching decisions. **USE CASES**: Implementing 'use cache' directive, configuring cache lifetimes with cacheLife(), tagging cached data with cacheTag(), invalidating caches with updateTag()/revalidateTag(), optimizing static vs dynamic content boundaries, debugging cache issues, and reviewing Cache Component implementations.

138.5k
Stats
Parent Repo Stars0
Parent Repo Forks0
Last CommitNov 23, 2025