Skip to content

Commit 4aab642

Browse files
committed
[compiler] Allow ref access in callbacks passed to event handler props (#35062)
## Summary Fixes #35040. The React compiler incorrectly flags ref access within event handlers as ref access at render time. For example, this code would fail to compile with error "Cannot access refs during render": ```tsx const onSubmit = async (data) => { const file = ref.current?.toFile(); // Incorrectly flagged as error }; <form onSubmit={handleSubmit(onSubmit)}> ``` This is a false positive because any built-in DOM event handler is guaranteed not to run at render time. This PR only supports built-in event handlers because there are no guarantees that user-made event handlers will not run at render time. ## How did you test this change? I created 4 test fixtures which validate this change: * allow-ref-access-in-event-handler-wrapper.tsx - Sync handler test input * allow-ref-access-in-event-handler-wrapper.expect.md - Sync handler expected output * allow-ref-access-in-async-event-handler-wrapper.tsx - Async handler test input * allow-ref-access-in-async-event-handler-wrapper.expect.md - Async handler expected output All linters and test suites also pass. DiffTrain build for [21f2824](21f2824)
1 parent 48774ee commit 4aab642

35 files changed

+123
-89
lines changed

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22091,8 +22091,9 @@ const BuiltInStartTransitionId = 'BuiltInStartTransition';
2209122091
const BuiltInFireId = 'BuiltInFire';
2209222092
const BuiltInFireFunctionId = 'BuiltInFireFunction';
2209322093
const BuiltInUseEffectEventId = 'BuiltInUseEffectEvent';
22094-
const BuiltinEffectEventId = 'BuiltInEffectEventFunction';
22094+
const BuiltInEffectEventId = 'BuiltInEffectEventFunction';
2209522095
const BuiltInAutodepsId = 'BuiltInAutoDepsId';
22096+
const BuiltInEventHandlerId = 'BuiltInEventHandlerId';
2209622097
const ReanimatedSharedValueId = 'ReanimatedSharedValueId';
2209722098
const BUILTIN_SHAPES = new Map();
2209822099
addObject(BUILTIN_SHAPES, BuiltInPropsId, [
@@ -22739,7 +22740,14 @@ addFunction(BUILTIN_SHAPES, [], {
2273922740
returnType: { kind: 'Poly' },
2274022741
calleeEffect: Effect.ConditionallyMutate,
2274122742
returnValueKind: ValueKind.Mutable,
22742-
}, BuiltinEffectEventId);
22743+
}, BuiltInEffectEventId);
22744+
addFunction(BUILTIN_SHAPES, [], {
22745+
positionalParams: [],
22746+
restParam: Effect.ConditionallyMutate,
22747+
returnType: { kind: 'Poly' },
22748+
calleeEffect: Effect.ConditionallyMutate,
22749+
returnValueKind: ValueKind.Mutable,
22750+
}, BuiltInEventHandlerId);
2274322751
addObject(BUILTIN_SHAPES, BuiltInMixedReadonlyId, [
2274422752
[
2274522753
'toString',
@@ -31169,7 +31177,7 @@ const REACT_APIS = [
3116931177
returnType: {
3117031178
kind: 'Function',
3117131179
return: { kind: 'Poly' },
31172-
shapeId: BuiltinEffectEventId,
31180+
shapeId: BuiltInEffectEventId,
3117331181
isConstructor: false,
3117431182
},
3117531183
calleeEffect: Effect.Read,
@@ -32192,6 +32200,7 @@ const EnvironmentConfigSchema = v4.z.object({
3219232200
validateNoVoidUseMemo: v4.z.boolean().default(true),
3219332201
validateNoDynamicallyCreatedComponentsOrHooks: v4.z.boolean().default(false),
3219432202
enableAllowSetStateFromRefsInEffects: v4.z.boolean().default(true),
32203+
enableInferEventHandlers: v4.z.boolean().default(false),
3219532204
});
3219632205
class Environment {
3219732206
constructor(scope, fnType, compilerMode, config, contextIdentifiers, parentFunction, logger, filename, code, programContext) {
@@ -48354,6 +48363,25 @@ function* generateInstructionTypes(env, names, instr) {
4835448363
}
4835548364
}
4835648365
}
48366+
if (env.config.enableInferEventHandlers) {
48367+
if (value.kind === 'JsxExpression' &&
48368+
value.tag.kind === 'BuiltinTag' &&
48369+
!value.tag.name.includes('-')) {
48370+
for (const prop of value.props) {
48371+
if (prop.kind === 'JsxAttribute' &&
48372+
prop.name.startsWith('on') &&
48373+
prop.name.length > 2 &&
48374+
prop.name[2] === prop.name[2].toUpperCase()) {
48375+
yield equation(prop.place.identifier.type, {
48376+
kind: 'Function',
48377+
shapeId: BuiltInEventHandlerId,
48378+
return: makeType(),
48379+
isConstructor: false,
48380+
});
48381+
}
48382+
}
48383+
}
48384+
}
4835748385
yield equation(left, { kind: 'Object', shapeId: BuiltInJsxId });
4835848386
break;
4835948387
}
@@ -49277,6 +49305,10 @@ function refTypeOfType(place) {
4927749305
return { kind: 'None' };
4927849306
}
4927949307
}
49308+
function isEventHandlerType(identifier) {
49309+
const type = identifier.type;
49310+
return type.kind === 'Function' && type.shapeId === BuiltInEventHandlerId;
49311+
}
4928049312
function tyEqual(a, b) {
4928149313
if (a.kind !== b.kind) {
4928249314
return false;
@@ -49563,8 +49595,10 @@ function validateNoRefAccessInRenderImpl(fn, env) {
4956349595
}
4956449596
if (!didError) {
4956549597
const isRefLValue = isUseRefType(instr.lvalue.identifier);
49598+
const isEventHandlerLValue = isEventHandlerType(instr.lvalue.identifier);
4956649599
for (const operand of eachInstructionValueOperand(instr.value)) {
4956749600
if (isRefLValue ||
49601+
isEventHandlerLValue ||
4956849602
(hookKind != null &&
4956949603
hookKind !== 'useState' &&
4957049604
hookKind !== 'useReducer')) {

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
257b033fc7b0518e7b1db32ca24e2354933b9d0e
1+
21f282425c751ee7926416642a0aded88d218623
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
257b033fc7b0518e7b1db32ca24e2354933b9d0e
1+
21f282425c751ee7926416642a0aded88d218623

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-257b033f-20251113";
1502+
exports.version = "19.3.0-www-classic-21f28242-20251114";
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-257b033f-20251113";
1502+
exports.version = "19.3.0-www-modern-21f28242-20251114";
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-257b033f-20251113";
609+
exports.version = "19.3.0-www-classic-21f28242-20251114";

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-257b033f-20251113";
609+
exports.version = "19.3.0-www-modern-21f28242-20251114";

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-257b033f-20251113";
613+
exports.version = "19.3.0-www-classic-21f28242-20251114";
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-257b033f-20251113";
613+
exports.version = "19.3.0-www-modern-21f28242-20251114";
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
@@ -20466,10 +20466,10 @@ __DEV__ &&
2046620466
(function () {
2046720467
var internals = {
2046820468
bundleType: 1,
20469-
version: "19.3.0-www-classic-257b033f-20251113",
20469+
version: "19.3.0-www-classic-21f28242-20251114",
2047020470
rendererPackageName: "react-art",
2047120471
currentDispatcherRef: ReactSharedInternals,
20472-
reconcilerVersion: "19.3.0-www-classic-257b033f-20251113"
20472+
reconcilerVersion: "19.3.0-www-classic-21f28242-20251114"
2047320473
};
2047420474
internals.overrideHookState = overrideHookState;
2047520475
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
@@ -20504,7 +20504,7 @@ __DEV__ &&
2050420504
exports.Shape = Shape;
2050520505
exports.Surface = Surface;
2050620506
exports.Text = Text;
20507-
exports.version = "19.3.0-www-classic-257b033f-20251113";
20507+
exports.version = "19.3.0-www-classic-21f28242-20251114";
2050820508
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
2050920509
"function" ===
2051020510
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

0 commit comments

Comments
 (0)