set-state-in-render

验证渲染过程中是否无条件设置状态,否则可能会触发额外的渲染并可能导致无限渲染循环。

¥Validates against unconditionally setting state during render, which can trigger additional renders and potential infinite render loops.

规则详情

¥Rule Details

在渲染过程中调用 setState 会在当前渲染完成之前无条件触发另一次渲染。这会创建一个无限循环,导致你的应用崩溃。

¥Calling setState during render unconditionally triggers another render before the current one finishes. This creates an infinite loop that crashes your app.

常见违规

¥Common Violations

无效

¥Invalid

// ❌ Unconditional setState directly in render
function Component({value}) {
const [count, setCount] = useState(0);
setCount(value); // Infinite loop!
return <div>{count}</div>;
}

有效

¥Valid

// ✅ Derive during render
function Component({items}) {
const sorted = [...items].sort(); // Just calculate it in render
return <ul>{sorted.map(/*...*/)}</ul>;
}

// ✅ Set state in event handler
function Component() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}

// ✅ Derive from props instead of setting state
function Component({user}) {
const name = user?.name || '';
const email = user?.email || '';
return <div>{name}</div>;
}

// ✅ Conditionally derive state from props and state from previous renders
function Component({ items }) {
const [isReverse, setIsReverse] = useState(false);
const [selection, setSelection] = useState(null);

const [prevItems, setPrevItems] = useState(items);
if (items !== prevItems) { // This condition makes it valid
setPrevItems(items);
setSelection(null);
}
// ...
}

故障排除

¥Troubleshooting

我想将状态同步到属性

¥I want to sync state to a prop

一个常见问题是尝试在渲染后获取 “fix” 状态。假设你想防止计数器超过 max prop:

¥A common problem is trying to “fix” state after it renders. Suppose you want to keep a counter from exceeding a max prop:

// ❌ Wrong: clamps during render
function Counter({max}) {
const [count, setCount] = useState(0);

if (count > max) {
setCount(max);
}

return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}

一旦 count 超过 max,就会触发无限循环。

¥As soon as count exceeds max, an infinite loop is triggered.

相反,通常最好将此逻辑移至事件(首次设置状态的地方)。例如,你可以在更新状态时强制执行最大值:

¥Instead, it’s often better to move this logic to the event (the place where the state is first set). For example, you can enforce the maximum at the moment you update state:

// ✅ Clamp 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>;
}

现在,setter 仅在点击时运行,React 正常完成渲染,并且 count 永远不会与 max 重叠。

¥Now the setter only runs in response to the click, React finishes the render normally, and count never crosses max.

在极少数情况下,你可能需要根据之前渲染的信息调整状态。对于这些,请遵循 这个模式 的条件设置状态。

¥In rare cases, you may need to adjust state based on information from previous renders. For those, follow this pattern of setting state conditionally.