Skip to content

Commit d9dc8f1

Browse files
committed
[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 DiffTrain build for [f99241b](f99241b)
1 parent d433bf7 commit d9dc8f1

35 files changed

+116
-96
lines changed

compiled/eslint-plugin-react-hooks/index.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32271,6 +32271,7 @@ const EnvironmentConfigSchema = v4.z.object({
3227132271
validateHooksUsage: v4.z.boolean().default(true),
3227232272
validateRefAccessDuringRender: v4.z.boolean().default(true),
3227332273
validateNoSetStateInRender: v4.z.boolean().default(true),
32274+
enableUseKeyedState: v4.z.boolean().default(false),
3227432275
validateNoSetStateInEffects: v4.z.boolean().default(false),
3227532276
validateNoDerivedComputationsInEffects: v4.z.boolean().default(false),
3227632277
validateNoDerivedComputationsInEffects_exp: v4.z.boolean().default(false),
@@ -50146,16 +50147,35 @@ function validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions) {
5014650147
}));
5014750148
}
5014850149
else if (unconditionalBlocks.has(block.id)) {
50149-
errors.pushDiagnostic(CompilerDiagnostic.create({
50150-
category: ErrorCategory.RenderSetState,
50151-
reason: 'Calling setState during render may trigger an infinite loop',
50152-
description: 'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)',
50153-
suggestions: null,
50154-
}).withDetails({
50155-
kind: 'error',
50156-
loc: callee.loc,
50157-
message: 'Found setState() in render',
50158-
}));
50150+
const enableUseKeyedState = fn.env.config.enableUseKeyedState;
50151+
if (enableUseKeyedState) {
50152+
errors.pushDiagnostic(CompilerDiagnostic.create({
50153+
category: ErrorCategory.RenderSetState,
50154+
reason: 'Cannot call setState during render',
50155+
description: 'Calling setState during render may trigger an infinite loop.\n' +
50156+
'* To reset state when other state/props change, use `const [state, setState] = useKeyedState(initialState, key)` to reset `state` when `key` changes.\n' +
50157+
'* To derive data from other state/props, compute the derived data during render without using state',
50158+
suggestions: null,
50159+
}).withDetails({
50160+
kind: 'error',
50161+
loc: callee.loc,
50162+
message: 'Found setState() in render',
50163+
}));
50164+
}
50165+
else {
50166+
errors.pushDiagnostic(CompilerDiagnostic.create({
50167+
category: ErrorCategory.RenderSetState,
50168+
reason: 'Cannot call setState during render',
50169+
description: 'Calling setState during render may trigger an infinite loop.\n' +
50170+
'* 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' +
50171+
'* To derive data from other state/props, compute the derived data during render without using state',
50172+
suggestions: null,
50173+
}).withDetails({
50174+
kind: 'error',
50175+
loc: callee.loc,
50176+
message: 'Found setState() in render',
50177+
}));
50178+
}
5015950179
}
5016050180
}
5016150181
break;

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
66ae640b362d75555649701e91c5ae26363db45b
1+
f99241b2e6cec825dfbdbb878755f12e1f510ca9
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
66ae640b362d75555649701e91c5ae26363db45b
1+
f99241b2e6cec825dfbdbb878755f12e1f510ca9

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,7 @@ __DEV__ &&
14991499
exports.useTransition = function () {
15001500
return resolveDispatcher().useTransition();
15011501
};
1502-
exports.version = "19.3.0-www-classic-66ae640b-20251204";
1502+
exports.version = "19.3.0-www-classic-f99241b2-20251204";
15031503
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15041504
"function" ===
15051505
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1499,7 +1499,7 @@ __DEV__ &&
14991499
exports.useTransition = function () {
15001500
return resolveDispatcher().useTransition();
15011501
};
1502-
exports.version = "19.3.0-www-modern-66ae640b-20251204";
1502+
exports.version = "19.3.0-www-modern-f99241b2-20251204";
15031503
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
15041504
"function" ===
15051505
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,4 +606,4 @@ exports.useSyncExternalStore = function (
606606
exports.useTransition = function () {
607607
return ReactSharedInternals.H.useTransition();
608608
};
609-
exports.version = "19.3.0-www-classic-66ae640b-20251204";
609+
exports.version = "19.3.0-www-classic-f99241b2-20251204";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -606,4 +606,4 @@ exports.useSyncExternalStore = function (
606606
exports.useTransition = function () {
607607
return ReactSharedInternals.H.useTransition();
608608
};
609-
exports.version = "19.3.0-www-modern-66ae640b-20251204";
609+
exports.version = "19.3.0-www-modern-f99241b2-20251204";

compiled/facebook-www/React-profiling.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ exports.useSyncExternalStore = function (
610610
exports.useTransition = function () {
611611
return ReactSharedInternals.H.useTransition();
612612
};
613-
exports.version = "19.3.0-www-classic-66ae640b-20251204";
613+
exports.version = "19.3.0-www-classic-f99241b2-20251204";
614614
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
615615
"function" ===
616616
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -610,7 +610,7 @@ exports.useSyncExternalStore = function (
610610
exports.useTransition = function () {
611611
return ReactSharedInternals.H.useTransition();
612612
};
613-
exports.version = "19.3.0-www-modern-66ae640b-20251204";
613+
exports.version = "19.3.0-www-modern-f99241b2-20251204";
614614
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
615615
"function" ===
616616
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20468,10 +20468,10 @@ __DEV__ &&
2046820468
(function () {
2046920469
var internals = {
2047020470
bundleType: 1,
20471-
version: "19.3.0-www-classic-66ae640b-20251204",
20471+
version: "19.3.0-www-classic-f99241b2-20251204",
2047220472
rendererPackageName: "react-art",
2047320473
currentDispatcherRef: ReactSharedInternals,
20474-
reconcilerVersion: "19.3.0-www-classic-66ae640b-20251204"
20474+
reconcilerVersion: "19.3.0-www-classic-f99241b2-20251204"
2047520475
};
2047620476
internals.overrideHookState = overrideHookState;
2047720477
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -20506,7 +20506,7 @@ __DEV__ &&
2050620506
exports.Shape = Shape;
2050720507
exports.Surface = Surface;
2050820508
exports.Text = Text;
20509-
exports.version = "19.3.0-www-classic-66ae640b-20251204";
20509+
exports.version = "19.3.0-www-classic-f99241b2-20251204";
2051020510
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
2051120511
"function" ===
2051220512
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)