ESLint plugin that enforces the Rules of Hooks for React applications with additional React Compiler rules
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
This document covers the two essential React Hooks rules that form the foundation of the eslint-plugin-react-hooks package.
Enforces that React Hooks are only called at the top level of React functions and not inside loops, conditions, or nested functions.
const RulesOfHooksRule: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description: 'enforces the Rules of Hooks',
recommended: true,
url: 'https://react.dev/reference/rules/rules-of-hooks'
}
},
create(context: RuleContext): NodeVisitor
};{
"rules": {
"react-hooks/rules-of-hooks": "error"
}
}// ✅ Valid: Hook called at top level of component
function MyComponent() {
const [state, setState] = useState(0);
useEffect(() => {
// Effect logic
});
return <div>{state}</div>;
}
// ✅ Valid: Hook called at top level of custom hook
function useCustomHook() {
const [state, setState] = useState(0);
return state;
}
// ✅ Valid: Hook called in component returned by HOC
function createComponent() {
return function Component() {
const [state, setState] = useState(0);
return <div>{state}</div>;
};
}// ❌ Invalid: Hook called conditionally
function MyComponent({ condition }) {
if (condition) {
const [state, setState] = useState(0); // Error!
}
}
// ❌ Invalid: Hook called in loop
function MyComponent({ items }) {
const states = [];
for (const item of items) {
states.push(useState(item)); // Error!
}
}
// ❌ Invalid: Hook called in regular function
function regularFunction() {
const [state, setState] = useState(0); // Error!
}Verifies that all dependencies of Hooks like useEffect, useMemo, and useCallback are included in their dependency arrays.
const ExhaustiveDepsRule: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
description: 'verifies the list of dependencies for Hooks like useEffect and similar',
recommended: true,
url: 'https://github.com/facebook/react/issues/14920'
},
fixable: 'code',
hasSuggestions: true,
schema: [
{
type: 'object',
additionalProperties: false,
properties: {
additionalHooks: {
type: 'string'
}
}
}
]
},
create(context: RuleContext): NodeVisitor
};{
"rules": {
"react-hooks/exhaustive-deps": "warn"
}
}Configure custom hooks that should be validated:
interface ExhaustiveDepsOptions {
additionalHooks?: string; // RegExp pattern for custom hooks
enableDangerousAutofixThisMayCauseInfiniteLoops?: boolean; // Enable legacy autofix
experimental_autoDependenciesHooks?: string[]; // Experimental auto-dependency hooks
requireExplicitEffectDeps?: boolean; // Require explicit dependency arrays
}{
"rules": {
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "(useMyCustomHook|useMyOtherCustomHook)"
}]
}
}// ✅ Valid: All dependencies included
function MyComponent({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // userId is included in dependencies
const memoizedValue = useMemo(() => {
return expensiveCalculation(user);
}, [user]); // user is included in dependencies
const handleClick = useCallback(() => {
console.log(user.name);
}, [user]); // user is included in dependencies
}
// ✅ Valid: Empty dependencies for effect that doesn't use any values
function MyComponent() {
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(timer);
}, []); // No dependencies needed
}// ❌ Invalid: Missing dependency
function MyComponent({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // Error: userId should be in dependencies
}
// ❌ Invalid: Stale closure
function MyComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log(count); // This will always log the initial value
}, []); // Error: count should be in dependencies
}
// ❌ Invalid: Unnecessary dependency
function MyComponent() {
const [count, setCount] = useState(0);
const stableValue = 42;
useEffect(() => {
console.log(count);
}, [count, stableValue]); // Error: stableValue is not needed
}The exhaustive-deps rule provides automatic fixes for common dependency issues:
For custom hooks that follow the same dependency patterns:
// Custom hook that should be validated
function useCustomEffect(callback, deps) {
useEffect(callback, deps);
}
// Configuration to validate this custom hook
{
"rules": {
"react-hooks/exhaustive-deps": ["warn", {
"additionalHooks": "useCustomEffect"
}]
}
}// Hook detection utilities
function isHookName(name: string): boolean;
function isHook(node: Node): boolean;
function isReactFunction(node: Node): boolean;
// Dependency analysis types
interface DeclaredDependency {
key: string;
node: Node;
}
interface Dependency {
isStable: boolean;
references: Array<Scope.Reference>;
}
interface DependencyTreeNode {
isUsed: boolean;
isSatisfiedRecursively: boolean;
isSubtreeUsed: boolean;
children: Map<string, DependencyTreeNode>;
}