logoESLint React
Rules

rules-of-hooks

Enforces the Rules of Hooks.

This rule is currently in rc and only available in v3.0.0 rc releases.

Full Name in eslint-plugin-react-x@rc

react-x/rules-of-hooks

Full Name in @eslint-react/eslint-plugin@rc

@eslint-react/rules-of-hooks

Features

⚙️ 🔧

Presets

x recommended recommended-typescript recommended-type-checked strict strict-typescript strict-type-checked

Rule Details

React relies on the order in which hooks are called to correctly preserve state between renders. Each time your component renders, React expects the exact same hooks to be called in the exact same order. When hooks are called conditionally or in loops, React loses track of which state corresponds to which hook call, leading to bugs like state mismatches and "Rendered fewer/more hooks than expected" errors.

Common Violations

Invalid

// ❌ Hook in condition
function MyComponent({ isLoggedIn }) {
  if (isLoggedIn) {
    const [user, setUser] = useState(null);
    //    ^^^ React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
  }
}
// ❌ Hook after early return
function MyComponent({ data }) {
  if (!data) return <Loading />;

  const [processed, setProcessed] = useState(data);
  //    ^^^ React Hook "useState" is called conditionally. React Hooks must be called in the exact same order in every component render.
}
// ❌ Hook in callback
function MyComponent() {
  return (
    <button
      onClick={() => {
        const [clicked, setClicked] = useState(false);
        //    ^^^ React Hook "useState" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.
      }}
    />
  );
}
// ❌ Hook in loop
function MyComponent({ items }) {
  for (const item of items) {
    const [selected, setSelected] = useState(false);
    //    ^^^ React Hook "useState" may be executed more than once. Possibly because it is called in a loop. React Hooks must be called in the exact same order in every component render.
  }
}
// ❌ `use` in try/catch
function MyComponent({ promise }) {
  try {
    const data = use(promise);
    //           ^^^ React Hook "use" cannot be called from a try block. Call it outside of the try block.
  } catch (e) {
    // error handling
  }
}

Valid

// ✅ Hooks at top level
function MyComponent({ isSpecial, shouldFetch, fetchPromise }) {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  if (!isSpecial) {
    return null;
  }

  if (shouldFetch) {
    // ✅ `use` can be conditional
    const data = use(fetchPromise);
    return <div>{data}</div>;
  }

  return (
    <div>
      {name}: {count}
    </div>
  );
}
// ✅ Custom hook calling other hooks at top level
function useUserData(userId) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);

  return user;
}

Troubleshooting

I want to fetch data based on some condition

You're trying to conditionally call useEffect:

// ❌ Conditional hook
function MyComponent({ isLoggedIn }) {
  if (isLoggedIn) {
    useEffect(() => {
      fetchUserData();
    }, []);
  }
}

Call the hook unconditionally, and check the condition inside:

// ✅ Condition inside hook
function MyComponent({ isLoggedIn }) {
  useEffect(() => {
    if (isLoggedIn) {
      fetchUserData();
    }
  }, [isLoggedIn]);
}

I need different state for different scenarios

You're trying to conditionally initialize state:

// ❌ Conditional state
function MyComponent({ userType, adminPerms, userPerms }) {
  if (userType === "admin") {
    const [permissions, setPermissions] = useState(adminPerms);
  } else {
    const [permissions, setPermissions] = useState(userPerms);
  }
}

Always call useState unconditionally, and set the initial value conditionally:

// ✅ Conditional initial value
function MyComponent({ userType, adminPerms, userPerms }) {
  const [permissions, setPermissions] = useState(
    userType === "admin" ? adminPerms : userPerms,
  );
}

Options

Rule Options

This rule accepts an optional configuration object:

type Options = {
  additionalHooks?: string;
};
{
  "react-x/rules-of-hooks": ["error", {
    "additionalHooks": "(useMyCustomHook|useAnotherHook)"
  }]
}

additionalHooks

A regex string matching custom hook names that should be validated under the same rules as built-in React hooks.

Note: When this rule-level option is specified, it takes precedence over the shared settings configuration described below.

Shared Settings

Custom effect hooks can also be configured via shared ESLint settings, which apply consistently across all rules in the plugin:

{
  "settings": {
    "react-x": {
      "additionalEffectHooks": "(useMyEffect|useCustomEffect)"
    }
  }
}

Resources

Further Reading


See Also

On this page