From c9d792a1f9ead5db32cf866168edc6c25372a0a6 Mon Sep 17 00:00:00 2001 From: dan Date: Wed, 1 Oct 2025 20:06:03 +0100 Subject: [PATCH] Conditionally deriving state is allowed --- .../lints/set-state-in-render.md | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md b/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md index c8cfa22a027..d5bd4f82abf 100644 --- a/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md +++ b/src/content/reference/eslint-plugin-react-hooks/lints/set-state-in-render.md @@ -5,7 +5,7 @@ version: rc -Validates against setting state during render, which can trigger additional renders and potential infinite render loops. +Validates against unconditionally setting state during render, which can trigger additional renders and potential infinite render loops. @@ -19,14 +19,14 @@ You can try it by upgrading the lint plugin [to the most recent RC version](/lea ## Rule Details {/*rule-details*/} -Calling `setState` during render triggers another render before the current one finishes. This creates an infinite loop that crashes your app. +Calling `setState` during render unconditionally triggers another render before the current one finishes. This creates an infinite loop that crashes your app. ## Common Violations {/*common-violations*/} ### Invalid {/*invalid*/} ```js {expectedErrors: {'react-compiler': [4]}} -// ❌ setState directly in render +// ❌ Unconditional setState directly in render function Component({value}) { const [count, setCount] = useState(0); setCount(value); // Infinite loop! @@ -59,6 +59,19 @@ function Component({user}) { const email = user?.email || ''; return
{name}
; } + +// ✅ 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 {/*troubleshooting*/} @@ -102,3 +115,5 @@ function Counter({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](https://react.dev/reference/react/useState#storing-information-from-previous-renders) of setting state conditionally.