@samitouri / QOS-React / commits / f99241b2e6

[compiler] Add enableUseKeyedState flag and improve setState-in-render errors (#35230)

Adds a new `enableUseKeyedState` compiler flag that changes the error message for unconditional setState calls during render. When `enableUseKeyedState` is enabled, the error recommends using `useKeyedState(initialState, key)` to reset state when dependencies change. When disabled (the default), it links to the React docs for the manual pattern of storing previous values in state. Both error messages now include helpful bullet points explaining the two main alternatives: 1. Use useKeyedState (or manual pattern) to reset state when other state/props change 2. Compute derived data directly during render without using state

lauren committed Dec 5, 2025 at 08:29 UTC f99241b2e6cec825dfbdbb878755f12e1f510ca9
13 files changed +146 -38
compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts
+6
@@ -318,6 +318,12 @@ export const EnvironmentConfigSchema = z.object({
318 */
319 validateNoSetStateInRender: z.boolean().default(true),
320
321 + /**
322 + * When enabled, changes the behavior of validateNoSetStateInRender to recommend
323 + * using useKeyedState instead of the manual pattern for resetting state.
324 + */
325 + enableUseKeyedState: z.boolean().default(false),
326 +
327 /**
328 * Validates that setState is not called synchronously within an effect (useEffect and friends).
329 * Scheduling a setState (with an event listener, subscription, etc) is valid.
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts
+34 -14
@@ -155,20 +155,40 @@ function validateNoSetStateInRenderImpl(
155 }),
156 );
157 } else if (unconditionalBlocks.has(block.id)) {
158 - errors.pushDiagnostic(
159 - CompilerDiagnostic.create({
160 - category: ErrorCategory.RenderSetState,
161 - reason:
162 - 'Calling setState during render may trigger an infinite loop',
163 - description:
164 - 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)',
165 - suggestions: null,
166 - }).withDetails({
167 - kind: 'error',
168 - loc: callee.loc,
169 - message: 'Found setState() in render',
170 - }),
171 - );
158 + const enableUseKeyedState = fn.env.config.enableUseKeyedState;
159 + if (enableUseKeyedState) {
160 + errors.pushDiagnostic(
161 + CompilerDiagnostic.create({
162 + category: ErrorCategory.RenderSetState,
163 + reason: 'Cannot call setState during render',
164 + description:
165 + 'Calling setState during render may trigger an infinite loop.\n' +
166 + '* To reset state when other state/props change, use `const [state, setState] = useKeyedState(initialState, key)` to reset `state` when `key` changes.\n' +
167 + '* To derive data from other state/props, compute the derived data during render without using state',
168 + suggestions: null,
169 + }).withDetails({
170 + kind: 'error',
171 + loc: callee.loc,
172 + message: 'Found setState() in render',
173 + }),
174 + );
175 + } else {
176 + errors.pushDiagnostic(
177 + CompilerDiagnostic.create({
178 + category: ErrorCategory.RenderSetState,
179 + reason: 'Cannot call setState during render',
180 + description:
181 + 'Calling setState during render may trigger an infinite loop.\n' +
182 + '* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders\n' +
183 + '* To derive data from other state/props, compute the derived data during render without using state',
184 + suggestions: null,
185 + }).withDetails({
186 + kind: 'error',
187 + loc: callee.loc,
188 + message: 'Found setState() in render',
189 + }),
190 + );
191 + }
192 }
193 }
194 break;
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-render-unbound-state.expect.md
+4 -2
@@ -24,9 +24,11 @@ export const FIXTURE_ENTRYPOINT = {
24 ```
25 Found 1 error:
26
27 -Error: Calling setState during render may trigger an infinite loop
27 +Error: Cannot call setState during render
28
29 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
29 +Calling setState during render may trigger an infinite loop.
30 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
31 +* To derive data from other state/props, compute the derived data during render without using state.
32
33 error.invalid-setState-in-render-unbound-state.ts:5:2
34 3 | // infer the type of destructured properties after a hole in the array
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setstate-unconditional-with-keyed-state.expect.md new
+44
@@ -0,0 +1,44 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @validateNoSetStateInRender @enableUseKeyedState
6 +import {useState} from 'react';
7 +
8 +function Component() {
9 + const [total, setTotal] = useState(0);
10 + setTotal(42);
11 + return total;
12 +}
13 +
14 +export const FIXTURE_ENTRYPOINT = {
15 + fn: Component,
16 + params: [],
17 + isComponent: true,
18 +};
19 +
20 +```
21 +
22 +
23 +## Error
24 +
25 +```
26 +Found 1 error:
27 +
28 +Error: Cannot call setState during render
29 +
30 +Calling setState during render may trigger an infinite loop.
31 +* To reset state when other state/props change, use `const [state, setState] = useKeyedState(initialState, key)` to reset `state` when `key` changes.
32 +* To derive data from other state/props, compute the derived data during render without using state.
33 +
34 +error.invalid-setstate-unconditional-with-keyed-state.ts:6:2
35 + 4 | function Component() {
36 + 5 | const [total, setTotal] = useState(0);
37 +> 6 | setTotal(42);
38 + | ^^^^^^^^ Found setState() in render
39 + 7 | return total;
40 + 8 | }
41 + 9 |
42 +```
43 +
44 +
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setstate-unconditional-with-keyed-state.js new
+14
@@ -0,0 +1,14 @@
1 +// @validateNoSetStateInRender @enableUseKeyedState
2 +import {useState} from 'react';
3 +
4 +function Component() {
5 + const [total, setTotal] = useState(0);
6 + setTotal(42);
7 + return total;
8 +}
9 +
10 +export const FIXTURE_ENTRYPOINT = {
11 + fn: Component,
12 + params: [],
13 + isComponent: true,
14 +};
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-hook-return-in-render.expect.md
+8 -4
@@ -25,9 +25,11 @@ function useCustomState(init) {
25 ```
26 Found 2 errors:
27
28 -Error: Calling setState during render may trigger an infinite loop
28 +Error: Cannot call setState during render
29
30 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
30 +Calling setState during render may trigger an infinite loop.
31 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
32 +* To derive data from other state/props, compute the derived data during render without using state.
33
34 error.invalid-unconditional-set-state-hook-return-in-render.ts:6:2
35 4 | const aliased = setState;
@@ -38,9 +40,11 @@ error.invalid-unconditional-set-state-hook-return-in-render.ts:6:2
40 8 |
41 9 | return state;
42
41 -Error: Calling setState during render may trigger an infinite loop
43 +Error: Cannot call setState during render
44
43 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
45 +Calling setState during render may trigger an infinite loop.
46 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
47 +* To derive data from other state/props, compute the derived data during render without using state.
48
49 error.invalid-unconditional-set-state-hook-return-in-render.ts:7:2
50 5 |
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md
+8 -4
@@ -21,9 +21,11 @@ function Component(props) {
21 ```
22 Found 2 errors:
23
24 -Error: Calling setState during render may trigger an infinite loop
24 +Error: Cannot call setState during render
25
26 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
26 +Calling setState during render may trigger an infinite loop.
27 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
28 +* To derive data from other state/props, compute the derived data during render without using state.
29
30 error.invalid-unconditional-set-state-in-render.ts:6:2
31 4 | const aliased = setX;
@@ -34,9 +36,11 @@ error.invalid-unconditional-set-state-in-render.ts:6:2
36 8 |
37 9 | return x;
38
37 -Error: Calling setState during render may trigger an infinite loop
39 +Error: Cannot call setState during render
40
39 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
41 +Calling setState during render may trigger an infinite loop.
42 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
43 +* To derive data from other state/props, compute the derived data during render without using state.
44
45 error.invalid-unconditional-set-state-in-render.ts:7:2
46 5 |
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-prop-in-render.expect.md
+8 -4
@@ -20,9 +20,11 @@ function Component({setX}) {
20 ```
21 Found 2 errors:
22
23 -Error: Calling setState during render may trigger an infinite loop
23 +Error: Cannot call setState during render
24
25 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
25 +Calling setState during render may trigger an infinite loop.
26 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
27 +* To derive data from other state/props, compute the derived data during render without using state.
28
29 error.invalid-unconditional-set-state-prop-in-render.ts:5:2
30 3 | const aliased = setX;
@@ -33,9 +35,11 @@ error.invalid-unconditional-set-state-prop-in-render.ts:5:2
35 7 |
36 8 | return x;
37
36 -Error: Calling setState during render may trigger an infinite loop
38 +Error: Cannot call setState during render
39
38 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
40 +Calling setState during render may trigger an infinite loop.
41 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
42 +* To derive data from other state/props, compute the derived data during render without using state.
43
44 error.invalid-unconditional-set-state-prop-in-render.ts:6:2
45 4 |
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md
+4 -2
@@ -24,9 +24,11 @@ function Component(props) {
24 ```
25 Found 1 error:
26
27 -Error: Calling setState during render may trigger an infinite loop
27 +Error: Cannot call setState during render
28
29 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
29 +Calling setState during render may trigger an infinite loop.
30 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
31 +* To derive data from other state/props, compute the derived data during render without using state.
32
33 error.unconditional-set-state-in-render-after-loop-break.ts:11:2
34 9 | }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md
+4 -2
@@ -19,9 +19,11 @@ function Component(props) {
19 ```
20 Found 1 error:
21
22 -Error: Calling setState during render may trigger an infinite loop
22 +Error: Cannot call setState during render
23
24 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
24 +Calling setState during render may trigger an infinite loop.
25 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
26 +* To derive data from other state/props, compute the derived data during render without using state.
27
28 error.unconditional-set-state-in-render-after-loop.ts:6:2
29 4 | for (const _ of props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md
+4 -2
@@ -24,9 +24,11 @@ function Component(props) {
24 ```
25 Found 1 error:
26
27 -Error: Calling setState during render may trigger an infinite loop
27 +Error: Cannot call setState during render
28
29 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
29 +Calling setState during render may trigger an infinite loop.
30 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
31 +* To derive data from other state/props, compute the derived data during render without using state.
32
33 error.unconditional-set-state-in-render-with-loop-throw.ts:11:2
34 9 | }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md
+4 -2
@@ -22,9 +22,11 @@ function Component(props) {
22 ```
23 Found 1 error:
24
25 -Error: Calling setState during render may trigger an infinite loop
25 +Error: Cannot call setState during render
26
27 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
27 +Calling setState during render may trigger an infinite loop.
28 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
29 +* To derive data from other state/props, compute the derived data during render without using state.
30
31 error.unconditional-set-state-lambda.ts:8:2
32 6 | setX(1);
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md
+4 -2
@@ -30,9 +30,11 @@ function Component(props) {
30 ```
31 Found 1 error:
32
33 -Error: Calling setState during render may trigger an infinite loop
33 +Error: Cannot call setState during render
34
35 -Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState).
35 +Calling setState during render may trigger an infinite loop.
36 +* To reset state when other state/props change, store the previous value in state and update conditionally: https://react.dev/reference/react/useState#storing-information-from-previous-renders
37 +* To derive data from other state/props, compute the derived data during render without using state.
38
39 error.unconditional-set-state-nested-function-expressions.ts:16:2
40 14 | bar();