Skip to content

Commit 6e1b4fd

Browse files
committed
[compiler] More flexible/helpful lazy ref initialization (#34449)
Two small QoL improvements inspired by feedback: * `if (ref.current === undefined) { ref.current = ... }` is now allowed. * `if (!ref.current) { ref.current = ... }` is still disallowed, but we emit an extra hint suggesting the `if (!ref.current == null)` pattern. I was on the fence about the latter. We got feedback asking to allow `if (!ref.current)` but if your ref stores a boolean value then this would allow reading the ref in render. The unary form is also less precise in general due to sketchy truthiness conversions. I figured a hint is a good compromise. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34449). * __->__ #34449 * #34424 DiffTrain build for [bd9e6e0](bd9e6e0)
1 parent 23223ad commit 6e1b4fd

35 files changed

+120
-86
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49448,12 +49448,46 @@ function validateNoRefAccessInRenderImpl(fn, env) {
4944849448
case 'StartMemoize':
4944949449
case 'FinishMemoize':
4945049450
break;
49451+
case 'LoadGlobal': {
49452+
if (instr.value.binding.name === 'undefined') {
49453+
env.set(instr.lvalue.identifier.id, { kind: 'Nullable' });
49454+
}
49455+
break;
49456+
}
4945149457
case 'Primitive': {
4945249458
if (instr.value.value == null) {
4945349459
env.set(instr.lvalue.identifier.id, { kind: 'Nullable' });
4945449460
}
4945549461
break;
4945649462
}
49463+
case 'UnaryExpression': {
49464+
if (instr.value.operator === '!') {
49465+
const value = env.get(instr.value.value.identifier.id);
49466+
const refId = (value === null || value === void 0 ? void 0 : value.kind) === 'RefValue' && value.refId != null
49467+
? value.refId
49468+
: null;
49469+
if (refId !== null) {
49470+
env.set(instr.lvalue.identifier.id, { kind: 'Guard', refId });
49471+
errors.pushDiagnostic(CompilerDiagnostic.create({
49472+
category: ErrorCategory.Refs,
49473+
reason: 'Cannot access refs during render',
49474+
description: ERROR_DESCRIPTION,
49475+
})
49476+
.withDetails({
49477+
kind: 'error',
49478+
loc: instr.value.value.loc,
49479+
message: `Cannot access ref value during render`,
49480+
})
49481+
.withDetails({
49482+
kind: 'hint',
49483+
message: 'To initialize a ref only once, check that the ref is null with the pattern `if (ref.current == null) { ref.current = ... }`',
49484+
}));
49485+
break;
49486+
}
49487+
}
49488+
validateNoRefValueAccess(errors, env, instr.value.value);
49489+
break;
49490+
}
4945749491
case 'BinaryExpression': {
4945849492
const left = env.get(instr.value.left.identifier.id);
4945949493
const right = env.get(instr.value.right.identifier.id);

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
835b00908b1b1c522323162b735a85c5f620ac81
1+
bd9e6e0bed6aed479940b1cf891dde82ab224028
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
835b00908b1b1c522323162b735a85c5f620ac81
1+
bd9e6e0bed6aed479940b1cf891dde82ab224028

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1418,7 +1418,7 @@ __DEV__ &&
14181418
exports.useTransition = function () {
14191419
return resolveDispatcher().useTransition();
14201420
};
1421-
exports.version = "19.2.0-www-classic-835b0090-20250910";
1421+
exports.version = "19.2.0-www-classic-bd9e6e0b-20250910";
14221422
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14231423
"function" ===
14241424
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
@@ -1418,7 +1418,7 @@ __DEV__ &&
14181418
exports.useTransition = function () {
14191419
return resolveDispatcher().useTransition();
14201420
};
1421-
exports.version = "19.2.0-www-modern-835b0090-20250910";
1421+
exports.version = "19.2.0-www-modern-bd9e6e0b-20250910";
14221422
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14231423
"function" ===
14241424
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
@@ -600,4 +600,4 @@ exports.useSyncExternalStore = function (
600600
exports.useTransition = function () {
601601
return ReactSharedInternals.H.useTransition();
602602
};
603-
exports.version = "19.2.0-www-classic-835b0090-20250910";
603+
exports.version = "19.2.0-www-classic-bd9e6e0b-20250910";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,4 +600,4 @@ exports.useSyncExternalStore = function (
600600
exports.useTransition = function () {
601601
return ReactSharedInternals.H.useTransition();
602602
};
603-
exports.version = "19.2.0-www-modern-835b0090-20250910";
603+
exports.version = "19.2.0-www-modern-bd9e6e0b-20250910";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ exports.useSyncExternalStore = function (
604604
exports.useTransition = function () {
605605
return ReactSharedInternals.H.useTransition();
606606
};
607-
exports.version = "19.2.0-www-classic-835b0090-20250910";
607+
exports.version = "19.2.0-www-classic-bd9e6e0b-20250910";
608608
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
609609
"function" ===
610610
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
@@ -604,7 +604,7 @@ exports.useSyncExternalStore = function (
604604
exports.useTransition = function () {
605605
return ReactSharedInternals.H.useTransition();
606606
};
607-
exports.version = "19.2.0-www-modern-835b0090-20250910";
607+
exports.version = "19.2.0-www-modern-bd9e6e0b-20250910";
608608
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
609609
"function" ===
610610
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
@@ -19708,10 +19708,10 @@ __DEV__ &&
1970819708
(function () {
1970919709
var internals = {
1971019710
bundleType: 1,
19711-
version: "19.2.0-www-classic-835b0090-20250910",
19711+
version: "19.2.0-www-classic-bd9e6e0b-20250910",
1971219712
rendererPackageName: "react-art",
1971319713
currentDispatcherRef: ReactSharedInternals,
19714-
reconcilerVersion: "19.2.0-www-classic-835b0090-20250910"
19714+
reconcilerVersion: "19.2.0-www-classic-bd9e6e0b-20250910"
1971519715
};
1971619716
internals.overrideHookState = overrideHookState;
1971719717
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -19745,7 +19745,7 @@ __DEV__ &&
1974519745
exports.Shape = Shape;
1974619746
exports.Surface = Surface;
1974719747
exports.Text = Text;
19748-
exports.version = "19.2.0-www-classic-835b0090-20250910";
19748+
exports.version = "19.2.0-www-classic-bd9e6e0b-20250910";
1974919749
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1975019750
"function" ===
1975119751
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)