Linter · Rules
react-hooks/set-state-in-render
Disallow calling a state setter unconditionally while a component renders
Setting state makes React render the component again. If the component sets state every time it renders, it never stops, and React gives up with "Too many re-renders".
The rule reports a setter that's always called during render, directly or through a function the component calls. It also reports every setter called in a useMemo() callback. It doesn't report a setter behind a condition, or one in an event handler or an effect.
Configure
Off by default. To turn it on:
bunfig.toml
[lint.rules]
"react-hooks/set-state-in-render" = "error" # or "warn"
Examples
Reported:
import { useState } from "react";
function Cart({ items }) {
const [count, setCount] = useState(0);
setCount(items.length);
return <span>{count}</span>;
}
A useMemo() callback runs during render too:
import { useMemo, useState } from "react";
function Cart({ items }) {
const [count, setCount] = useState(0);
const total = useMemo(() => {
setCount(items.length);
return items.reduce((sum, item) => sum + item.price, 0);
}, [items]);
return <span>{count} items, {total}</span>;
}
Not reported:
function Cart({ items }) {
const count = items.length;
return <span>{count}</span>;
}
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
A setter behind a condition is how you reset state when a prop changes. The condition is false on the next render, so the loop ends:
import { useState } from "react";
function List({ items }) {
const [previous, setPrevious] = useState(items);
const [selected, setSelected] = useState(null);
if (items !== previous) {
setPrevious(items);
setSelected(null);
}
return <ul onClick={() => setSelected(0)}>{selected}</ul>;
}