set-state-in-render
Validates against unconditionally setting state during render, which can trigger additional renders and potential infinite render loops.
This rule is experimental and may change in the future or be removed. It is not recommended for use in production code at this time.
Full Name in eslint-plugin-react-x
react-x/set-state-in-renderFull Name in @eslint-react/eslint-plugin
@eslint-react/set-state-in-renderFeatures
🧪
Presets
x
recommended
recommended-typescript
recommended-type-checked
strict
strict-typescript
strict-type-checked
Rule Details
Calling setState during render unconditionally triggers another render before the current one finishes. This creates an infinite loop that crashes your app.
Examples
Unconditional setState in render
Calling a state setter directly in the component body, without any guard condition, will trigger a re-render immediately, leading to an infinite loop.
// Problem: unconditionally calling setState during render
function Component({ value }) {
const [count, setCount] = useState(0);
setCount(value); // infinite loop!
return <div>{count}</div>;
}// Recommended: derive values directly during rendering
function Component({ items }) {
const sorted = [...items].sort();
return <ul>{sorted.map(/*...*/)}</ul>;
}// Recommended: set state in event handlers
function Component() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}Deriving values from props
If you only need to transform props before displaying them, compute the value directly during rendering instead of copying it into state.
// Recommended: derive values directly during rendering
function Component({ user }) {
const name = user?.name || "";
const email = user?.email || "";
return <div>{name}</div>;
}Syncing state to a prop
A common mistake is trying to "fix" state after it renders. For example, clamping a counter to a max prop during render causes an infinite loop as soon as the value exceeds the limit.
// Problem: adjusting state during render
function Counter({ max }) {
const [count, setCount] = useState(0);
if (count > max) {
setCount(max); // triggers infinite loop when exceeding max
}
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}// Recommended: limit the value when updating
function Counter({ max }) {
const [count, setCount] = useState(0);
const increment = () => {
setCount(current => Math.min(current + 1, max));
};
return <button onClick={increment}>{count}</button>;
}Conditional state updates during render
In rare cases, you may need to adjust state based on information from previous renders. This is valid only when guarded by a condition that prevents infinite loops.
// OK: conditionally updating state based on information from the previous render
function Component({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);
const [prevItems, setPrevItems] = useState(items);
if (items !== prevItems) { // condition prevents infinite loop
setPrevItems(items);
setSelection(null);
}
// ...
}// Recommended: compute directly during rendering whenever possible
function Component({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selectedId, setSelectedId] = useState(null);
const selection = items.find((item) => item.id === selectedId) ?? null;
// ...
}Options
Shared Settings
Custom state hooks can also be configured via shared ESLint settings, which apply consistently across all rules in the plugin:
{
"settings": {
"react-x": {
"additionalStateHooks": "(useMyState|useCustomState)"
}
}
}Versions
Resources
Further Reading
- React Docs:
useState - React Docs: Storing information from previous renders
- React Docs:
set-state-in-renderLint Rule
See Also
react-x/set-state-in-effect
Validates against setting state synchronously in an effect, which can lead to re-renders that degrade performance.