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-hooksFull Name in @eslint-react/eslint-plugin@rc
@eslint-react/rules-of-hooksFeatures
⚙️ 🔧
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
- React Docs: Rules of Hooks
- React Docs:
useState - React Docs:
useEffect - React Docs:
use - React Docs:
rules-of-hooksLint Rule
See Also
react-x/exhaustive-deps
Verifies the list of dependencies for Hooks likeuseEffectand similar.react-x/no-unnecessary-use-prefix
Enforces that a function with theuseprefix uses at least one Hook inside it.