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

一个常见的问题是在渲染后尝试“修复”状态。假设你想防止计数器超过 max 属性:

🌐 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.