additionalEffectHooks ESLint configuration
Custom effect hooks can be configured using the 'react-hooks' shared ESLint settings with an 'additionalEffectHooks' property that accepts a regex pattern. This allows custom hooks like useEffectEvent to be treated as effects by both rules-of-hooks and exhaustive-deps rules. Available in eslint-plugin-react-hooks 6.1.1 and later.
use hook conditional example
The use hook can be called conditionally: `if (shouldFetch) { const data = use(fetchPromise); }` and can be called in loops: `for (const promise of promises) { results.push(use(promise)); }`
Hooks in conditions invalid example
This violates the Rules of Hooks: `if (isLoggedIn) { const [user, setUser] = useState(null); }`
Hooks after early return invalid example
This violates the Rules of Hooks: `if (!data) return <Loading />; const [processed, setProcessed] = useState(data);`
Hooks in callback invalid example
This violates the Rules of Hooks: `<button onClick={() => { const [clicked, setClicked] = useState(false); }}/>`
use hook in try/catch invalid example
This violates the Rules of Hooks: `try { const data = use(promise); } catch (e) { // error handling }`
Hooks at module level invalid example
This violates the Rules of Hooks: `const globalState = useState(0);` when called outside a component.
Valid hooks at top level example
This follows the Rules of Hooks: `function Component({ isSpecial, shouldFetch, fetchPromise }) { const [count, setCount] = useState(0); const [name, setName] = useState(''); if (!isSpecial) { return null; } if (shouldFetch) { const data = use(fetchPromise); return <div>{data}</div>; } return <div>{name}: {count}</div>; }`
Conditional useEffect fix example
Instead of `if (isLoggedIn) { useEffect(() => { fetchUserData(); }, []); }`, use: `useEffect(() => { if (isLoggedIn) { fetchUserData(); } }, [isLoggedIn]);`
Conditional state initialization fix example
Instead of conditionally calling useState for different scenarios, use: `const [permissions, setPermissions] = useState(userType === 'admin' ? adminPerms : userPerms);`
set-state-in-render: solution - enforce constraints in event handlers
Move clamping logic to event handlers where state is first set. Valid pattern: function Counter({max}) { const [count, setCount] = useState(0); const increment = () => { setCount(current => Math.min(current + 1, max)); }; return <button onClick={increment}>{count}</button>; } The setter only runs in response to the click, React finishes the render normally, and count never crosses max.
set-state-in-render rule: unconditional setState triggers infinite loops
The eslint-plugin-react-hooks rule 'set-state-in-render' validates against unconditionally setting state during render. Calling setState during render unconditionally triggers another render before the current one finishes, creating an infinite loop that crashes the app.
set-state-in-render: invalid pattern - unconditional setState in render
Unconditional setState directly in render is invalid. Example: function Component({value}) { const [count, setCount] = useState(0); setCount(value); return <div>{count}</div>; } This creates an infinite loop.
set-state-in-render: valid pattern - derive values during render
Instead of setting state unconditionally, derive values during render. Example: function Component({items}) { const sorted = [...items].sort(); return <ul>{sorted.map(/*...*/)} </ul>; }
set-state-in-render: valid pattern - setState in event handlers
Set state in event handlers instead of during render. Example: function Component() { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; }
set-state-in-render: valid pattern - derive from props instead of setting state
Derive values from props instead of setting state. Example: function Component({user}) { const name = user?.name || ''; const email = user?.email || ''; return <div>{name}</div>; }
set-state-in-render: valid pattern - conditionally derive state from props
Conditionally set state from props and previous renders when a condition makes it valid. Example: function Component({ items }) { const [isReverse, setIsReverse] = useState(false); const [selection, setSelection] = useState(null); const [prevItems, setPrevItems] = useState(items); if (items !== prevItems) { setPrevItems(items); setSelection(null); } ... } The condition checking that items changed makes this valid.
set-state-in-render: pitfall - clamping state during render causes infinite loop
Attempting to clamp state during render causes an infinite loop. Invalid pattern: function Counter({max}) { const [count, setCount] = useState(0); if (count > max) { setCount(max); } return <button onClick={() => setCount(count + 1)}>{count}</button>; } As soon as count exceeds max, an infinite loop is triggered.
static-components rule validates components are not recreated every render
The static-components ESLint rule from eslint-plugin-react-hooks validates that components are static and not recreated on every render. Components that are recreated dynamically can reset state and trigger excessive re-rendering.
Components defined inside other components are recreated every render
When a component is defined inside another component, it is recreated on every render. React sees each instance as a brand new component type, unmounting the old one and mounting the new one, which destroys all state and DOM nodes in the process.
static-components invalid pattern: component defined inside parent
Defining a component inside a parent component causes the child component to be recreated every render, resetting its state. Example: function Parent() { const ChildComponent = () => { const [count, setCount] = useState(0); return <button onClick={() => setCount(count + 1)}>{count}</button>; }; return <ChildComponent />; }
static-components invalid pattern: dynamic component creation
Creating different components conditionally based on props causes those components to be recreated every render. Example: function Parent({type}) { const Component = type === 'button' ? () => <button>Click</button> : () => <div>Text</div>; return <Component />; }
static-components valid pattern: reference existing components
Define components at module level and reference them by name instead of creating them dynamically. Example: const ButtonComponent = () => <button>Click</button>; const TextComponent = () => <div>Text</div>; function Parent({type}) { const Component = type === 'button' ? ButtonComponent : TextComponent; return <Component />; }
static-components solution: pass data as props instead of defining components inside
Instead of defining a component inside a parent component to access parent state, pass the data as props to a static component. This makes components more reusable and testable. Example: function ThemedButton({theme}) { return <button className={theme}>Click me</button>; } function Parent() { const [theme, setTheme] = useState('light'); return <ThemedButton theme={theme} />; }
Example: eval in component should use safe parser instead
Invalid: function Calculator({expression}) { const result = eval(expression); return <div>Result: {result}</div>; }
Valid: import {evaluate} from 'mathjs';
function Calculator({expression}) {
const [result, setResult] = useState(null);
const calculate = () => {
try {
setResult(evaluate(expression));
} catch (error) {
setResult('Invalid expression');
}
};
return (
<div>
<button onClick={calculate}>Calculate</button>
{result && <div>Result: {result}</div>}
</div>
);
}
This example shows how to safely evaluate user-provided mathematical expressions using a dedicated library instead of eval.
unsupported-syntax rule validates against React Compiler incompatible syntax
The unsupported-syntax rule validates against syntax that React Compiler does not support. Features like eval and with statements make it impossible for the compiler to statically analyze code at compile time, so components using them cannot be optimized. The rule allows using such syntax outside of React, such as in standalone utility functions.
eval in components cannot be analyzed by React Compiler
Using eval() in a component is invalid because the React Compiler cannot statically analyze what code eval executes at compile time. This prevents the compiler from applying optimizations.
with statement changes scope dynamically and cannot be analyzed
Using a with statement in a component is invalid because it changes scope dynamically in ways that cannot be statically understood by the React Compiler.
Dynamic property access with computed keys is analyzable
Use computed property access like props[propName] instead of eval for dynamic property access. Computed property access can be statically analyzed by React Compiler.
Never use eval with user input - it is a security risk
Using eval with user-provided input is both a security risk and makes code unoptimizable. Instead, use dedicated parsing libraries for specific use cases like mathematical expressions, JSON parsing, or template evaluation.
use-memo lint rule validates return value requirement
The use-memo eslint-plugin-react-hooks lint rule validates that the useMemo hook is used with a return value. useMemo is for computing and caching expensive values, not for side effects. Without a return value, useMemo returns undefined, which defeats its purpose and indicates you are using the wrong hook.
useMemo without return value is an error
A useMemo callback that does not return a value is invalid. For example, iterating over data with forEach and logging items without a return statement means useMemo returns undefined, making the hook pointless. This pattern indicates you should use useEffect instead.
useMemo correct usage example with return value
Example of correct useMemo usage:
function Component({ data }) {
const processed = useMemo(() => {
return data.map(item => item * 2);
}, [data]);
return <div>{processed}</div>;
}
The callback must return a computed value that is then assigned to a variable.
Do not use useMemo for side effects
useMemo should not be used to perform side effects. Side effects in useMemo are wrong because the hook is designed for computing and caching values, not for running code for its effects. If a side effect needs to happen in response to user interaction, colocate it with the event handler. If a side effect synchronizes React state with external state, use useEffect instead.
useMemo side effect antipattern examples
Incorrect useMemo patterns for side effects:
1. No return value, just side effect:
useMemo(() => {
analytics.track('UserViewed', {userId: user.id});
}, [user.id]);
2. Returning the side effect result without assigning to variable:
useMemo(() => {
return analytics.track('UserViewed', {userId: user.id});
}, [user.id]);
Both patterns indicate useMemo is being misused for side effects rather than value computation.
Use useEffect for synchronizing React state with external state
When a side effect needs to synchronize React state with some external state or vice versa, use useEffect instead of useMemo. For example, to persist theme preference to localStorage and update the DOM when theme changes:
useEffect(() => {
localStorage.setItem('preferredTheme', theme);
document.body.className = theme;
}, [theme]);
Use event handlers for side effects from user interaction
When a side effect needs to happen in response to user interaction, colocate the side effect with the event handler. For example:
const handleClick = () => {
analytics.track('ButtonClicked', {userId: user.id});
// Other click logic...
};
return <button onClick={handleClick}>Click me</button>;
ESLint Plugin React Hooks purpose
The ESLint plugin for React Hooks helps enforce the Rules of React, with detailed documentation for each lint including examples.
eslint-plugin-react-hooks enforcement for useEffectEvent
The eslint-plugin-react-hooks linter enforces the restrictions on Effect Events: they can only be called from inside Effects or other Effect Events, not during rendering or passed to other components or Hooks.
eslint-plugin-react-hooks for catching rule violations
The eslint-plugin-react-hooks plugin can be used to catch violations of the Rules of Hooks.