Skip to content

Commit 9718c76

Browse files
committed
[compiler] Add hint to name variables with "Ref" suffix (#34125)
If you have a ref that the compiler doesn't know is a ref (say, a value returned from a custom hook) and try to assign its `.current = ...`, we currently fail with a generic error that hook return values are not mutable. However, an assignment to `.current` specifically is a very strong hint that the value is likely to be a ref. So in this PR, we track the reason for the mutation and if it ends up being an error, we use it to show an additional hint to the user. See the fixture for an example of the message. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34125). * #34126 * __->__ #34125 * #34124 DiffTrain build for [724b324](724b324)
1 parent 34f3f17 commit 9718c76

35 files changed

+132
-107
lines changed

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

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -42119,7 +42119,7 @@ function inferBlock(context, state, block) {
4211942119
}
4212042120
}
4212142121
function applySignature(context, state, signature, instruction) {
42122-
var _a;
42122+
var _a, _b;
4212342123
const effects = [];
4212442124
if (instruction.value.kind === 'FunctionExpression' ||
4212542125
instruction.value.kind === 'ObjectMethod') {
@@ -42142,18 +42142,27 @@ function applySignature(context, state, signature, instruction) {
4214242142
effect.value.identifier.name.kind === 'named'
4214342143
? `\`${effect.value.identifier.name.value}\``
4214442144
: 'value';
42145+
const diagnostic = CompilerDiagnostic.create({
42146+
severity: ErrorSeverity.InvalidReact,
42147+
category: 'This value cannot be modified',
42148+
description: `${reason}.`,
42149+
}).withDetail({
42150+
kind: 'error',
42151+
loc: effect.value.loc,
42152+
message: `${variable} cannot be modified`,
42153+
});
42154+
if (effect.kind === 'Mutate' &&
42155+
((_b = effect.reason) === null || _b === void 0 ? void 0 : _b.kind) === 'AssignCurrentProperty') {
42156+
diagnostic.withDetail({
42157+
kind: 'error',
42158+
loc: effect.value.loc,
42159+
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
42160+
});
42161+
}
4214542162
effects.push({
4214642163
kind: 'MutateFrozen',
4214742164
place: effect.value,
42148-
error: CompilerDiagnostic.create({
42149-
severity: ErrorSeverity.InvalidReact,
42150-
category: 'This value cannot be modified',
42151-
description: `${reason}.`,
42152-
}).withDetail({
42153-
kind: 'error',
42154-
loc: effect.value.loc,
42155-
message: `${variable} cannot be modified`,
42156-
}),
42165+
error: diagnostic,
4215742166
});
4215842167
}
4215942168
}
@@ -42173,7 +42182,7 @@ function applySignature(context, state, signature, instruction) {
4217342182
return effects.length !== 0 ? effects : null;
4217442183
}
4217542184
function applyEffect(context, state, _effect, initialized, effects) {
42176-
var _a, _b, _c, _d;
42185+
var _a, _b, _c, _d, _e;
4217742186
const effect = context.internEffect(_effect);
4217842187
switch (effect.kind) {
4217942188
case 'Freeze': {
@@ -42562,20 +42571,29 @@ function applyEffect(context, state, _effect, initialized, effects) {
4256242571
effect.value.identifier.name.kind === 'named'
4256342572
? `\`${effect.value.identifier.name.value}\``
4256442573
: 'value';
42574+
const diagnostic = CompilerDiagnostic.create({
42575+
severity: ErrorSeverity.InvalidReact,
42576+
category: 'This value cannot be modified',
42577+
description: `${reason}.`,
42578+
}).withDetail({
42579+
kind: 'error',
42580+
loc: effect.value.loc,
42581+
message: `${variable} cannot be modified`,
42582+
});
42583+
if (effect.kind === 'Mutate' &&
42584+
((_e = effect.reason) === null || _e === void 0 ? void 0 : _e.kind) === 'AssignCurrentProperty') {
42585+
diagnostic.withDetail({
42586+
kind: 'error',
42587+
loc: effect.value.loc,
42588+
message: `Hint: If this value is a Ref (value returned by \`useRef()\`), rename the variable to end in "Ref".`,
42589+
});
42590+
}
4256542591
applyEffect(context, state, {
4256642592
kind: value.kind === ValueKind.Frozen
4256742593
? 'MutateFrozen'
4256842594
: 'MutateGlobal',
4256942595
place: effect.value,
42570-
error: CompilerDiagnostic.create({
42571-
severity: ErrorSeverity.InvalidReact,
42572-
category: 'This value cannot be modified',
42573-
description: `${reason}.`,
42574-
}).withDetail({
42575-
kind: 'error',
42576-
loc: effect.value.loc,
42577-
message: `${variable} cannot be modified`,
42578-
}),
42596+
error: diagnostic,
4257942597
}, initialized, effects);
4258042598
}
4258142599
}
@@ -43032,7 +43050,14 @@ function computeSignatureForInstruction(context, env, instr) {
4303243050
}
4303343051
case 'PropertyStore':
4303443052
case 'ComputedStore': {
43035-
effects.push({ kind: 'Mutate', value: value.object });
43053+
const mutationReason = value.kind === 'PropertyStore' && value.property === 'current'
43054+
? { kind: 'AssignCurrentProperty' }
43055+
: null;
43056+
effects.push({
43057+
kind: 'Mutate',
43058+
value: value.object,
43059+
reason: mutationReason,
43060+
});
4303643061
effects.push({
4303743062
kind: 'Capture',
4303843063
from: value.value,

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
45a6532a088432010c40cbcac1fb6b6f8d56dd69
1+
724b324b966343f08ff0cb16ec9d8013e3891dfd
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
45a6532a088432010c40cbcac1fb6b6f8d56dd69
1+
724b324b966343f08ff0cb16ec9d8013e3891dfd

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1409,7 +1409,7 @@ __DEV__ &&
14091409
exports.useTransition = function () {
14101410
return resolveDispatcher().useTransition();
14111411
};
1412-
exports.version = "19.2.0-www-classic-45a6532a-20250815";
1412+
exports.version = "19.2.0-www-classic-724b324b-20250815";
14131413
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14141414
"function" ===
14151415
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
@@ -1409,7 +1409,7 @@ __DEV__ &&
14091409
exports.useTransition = function () {
14101410
return resolveDispatcher().useTransition();
14111411
};
1412-
exports.version = "19.2.0-www-modern-45a6532a-20250815";
1412+
exports.version = "19.2.0-www-modern-724b324b-20250815";
14131413
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
14141414
"function" ===
14151415
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-45a6532a-20250815";
603+
exports.version = "19.2.0-www-classic-724b324b-20250815";

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-45a6532a-20250815";
603+
exports.version = "19.2.0-www-modern-724b324b-20250815";

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-45a6532a-20250815";
607+
exports.version = "19.2.0-www-classic-724b324b-20250815";
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-45a6532a-20250815";
607+
exports.version = "19.2.0-www-modern-724b324b-20250815";
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
@@ -19588,10 +19588,10 @@ __DEV__ &&
1958819588
(function () {
1958919589
var internals = {
1959019590
bundleType: 1,
19591-
version: "19.2.0-www-classic-45a6532a-20250815",
19591+
version: "19.2.0-www-classic-724b324b-20250815",
1959219592
rendererPackageName: "react-art",
1959319593
currentDispatcherRef: ReactSharedInternals,
19594-
reconcilerVersion: "19.2.0-www-classic-45a6532a-20250815"
19594+
reconcilerVersion: "19.2.0-www-classic-724b324b-20250815"
1959519595
};
1959619596
internals.overrideHookState = overrideHookState;
1959719597
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -19625,7 +19625,7 @@ __DEV__ &&
1962519625
exports.Shape = Shape;
1962619626
exports.Surface = Surface;
1962719627
exports.Text = Text;
19628-
exports.version = "19.2.0-www-classic-45a6532a-20250815";
19628+
exports.version = "19.2.0-www-classic-724b324b-20250815";
1962919629
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
1963019630
"function" ===
1963119631
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)