Skip to content

Commit 0f346a4

Browse files
committed
Add ref to Fragment (#32465)
*This API is experimental and subject to change or removal.* This PR is an alternative to #32421 based on feedback: #32421 (review) . The difference here is that we traverse from the Fragment's fiber at operation time instead of keeping a set of children on the `FragmentInstance`. We still need to handle newly added or removed child nodes to apply event listeners and observers, so we treat those updates as effects. **Fragment Refs** This PR extends React's Fragment component to accept a `ref` prop. The Fragment's ref will attach to a custom host instance, which will provide an Element-like API for working with the Fragment's host parent and host children. Here I've implemented `addEventListener`, `removeEventListener`, and `focus` to get started but we'll be iterating on this by adding additional APIs in future PRs. This sets up the mechanism to attach refs and perform operations on children. The FragmentInstance is implemented in `react-dom` here but is planned for Fabric as well. The API works by targeting the first level of host children and proxying Element-like APIs to allow developers to manage groups of elements or elements that cannot be easily accessed such as from a third-party library or deep in a tree of Functional Component wrappers. ```javascript import {Fragment, useRef} from 'react'; const fragmentRef = useRef(null); <Fragment ref={fragmentRef}> <div id="A" /> <Wrapper> <div id="B"> <div id="C" /> </div> </Wrapper> <div id="D" /> </Fragment> ``` In this case, calling `fragmentRef.current.addEventListener()` would apply an event listener to `A`, `B`, and `D`. `C` is skipped because it is nested under the first level of Host Component. If another Host Component was appended as a sibling to `A`, `B`, or `D`, the event listener would be applied to that element as well and any other APIs would also affect the newly added child. This is an implementation of the basic feature as a starting point for feedback and further iteration. DiffTrain build for [6aa8254](6aa8254)
1 parent dd554f3 commit 0f346a4

23 files changed

+855
-642
lines changed

compiled-rn/VERSION_NATIVE_FB

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
19.1.0-native-fb-99e10240-20250310
1+
19.1.0-native-fb-6aa8254b-20250312

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOM-dev.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<7326694dd3f20da9625c2a82ce71b6f4>>
10+
* @generated SignedSource<<aff93e86887778b25fa3ec875917976c>>
1111
*/
1212

1313
"use strict";
@@ -404,5 +404,5 @@ __DEV__ &&
404404
exports.useFormStatus = function () {
405405
return resolveDispatcher().useHostTransitionStatus();
406406
};
407-
exports.version = "19.1.0-native-fb-99e10240-20250310";
407+
exports.version = "19.1.0-native-fb-6aa8254b-20250312";
408408
})();

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOM-prod.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<c9eb52ca6f71e3e180ad612120eac82d>>
10+
* @generated SignedSource<<121564ae6ab61ad05a1f38012aaafb25>>
1111
*/
1212

1313
"use strict";
@@ -203,4 +203,4 @@ exports.useFormState = function (action, initialState, permalink) {
203203
exports.useFormStatus = function () {
204204
return ReactSharedInternals.H.useHostTransitionStatus();
205205
};
206-
exports.version = "19.1.0-native-fb-99e10240-20250310";
206+
exports.version = "19.1.0-native-fb-6aa8254b-20250312";

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOM-profiling.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<c9eb52ca6f71e3e180ad612120eac82d>>
10+
* @generated SignedSource<<121564ae6ab61ad05a1f38012aaafb25>>
1111
*/
1212

1313
"use strict";
@@ -203,4 +203,4 @@ exports.useFormState = function (action, initialState, permalink) {
203203
exports.useFormStatus = function () {
204204
return ReactSharedInternals.H.useHostTransitionStatus();
205205
};
206-
exports.version = "19.1.0-native-fb-99e10240-20250310";
206+
exports.version = "19.1.0-native-fb-6aa8254b-20250312";

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOMClient-dev.js

Lines changed: 207 additions & 185 deletions
Large diffs are not rendered by default.

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOMClient-prod.js

Lines changed: 41 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<bf9ab699b4da639cbd5f6eb49a0eb99c>>
10+
* @generated SignedSource<<43c54c242b15b6947c2c57dd1feb75cb>>
1111
*/
1212

1313
/*
@@ -7886,6 +7886,9 @@ function safelyAttachRef(current, nearestMountedAncestor) {
78867886
case 5:
78877887
var instanceToUse = current.stateNode;
78887888
break;
7889+
case 30:
7890+
instanceToUse = current.stateNode;
7891+
break;
78897892
default:
78907893
instanceToUse = current.stateNode;
78917894
}
@@ -8350,6 +8353,8 @@ function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork) {
83508353
? safelyAttachRef(finishedWork, finishedWork.return)
83518354
: safelyDetachRef(finishedWork, finishedWork.return));
83528355
break;
8356+
case 30:
8357+
break;
83538358
default:
83548359
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
83558360
}
@@ -9079,6 +9084,7 @@ function commitMutationEffectsOnFiber(finishedWork, root) {
90799084
attachSuspenseRetryListeners(finishedWork, flags)));
90809085
break;
90819086
case 30:
9087+
break;
90829088
case 21:
90839089
break;
90849090
default:
@@ -9090,33 +9096,34 @@ function commitReconciliationEffects(finishedWork) {
90909096
var flags = finishedWork.flags;
90919097
if (flags & 2) {
90929098
try {
9093-
a: {
9094-
for (var parent = finishedWork.return; null !== parent; ) {
9095-
if (isHostParent(parent)) {
9096-
var JSCompiler_inline_result = parent;
9097-
break a;
9098-
}
9099-
parent = parent.return;
9099+
for (
9100+
var hostParentFiber, parentFiber = finishedWork.return;
9101+
null !== parentFiber;
9102+
9103+
) {
9104+
if (isHostParent(parentFiber)) {
9105+
hostParentFiber = parentFiber;
9106+
break;
91009107
}
9101-
throw Error(formatProdErrorMessage(160));
9108+
parentFiber = parentFiber.return;
91029109
}
9103-
switch (JSCompiler_inline_result.tag) {
9110+
if (null == hostParentFiber) throw Error(formatProdErrorMessage(160));
9111+
switch (hostParentFiber.tag) {
91049112
case 27:
9105-
var parent$jscomp$0 = JSCompiler_inline_result.stateNode,
9113+
var parent = hostParentFiber.stateNode,
91069114
before = getHostSibling(finishedWork);
9107-
insertOrAppendPlacementNode(finishedWork, before, parent$jscomp$0);
9115+
insertOrAppendPlacementNode(finishedWork, before, parent);
91089116
break;
91099117
case 5:
9110-
var parent$125 = JSCompiler_inline_result.stateNode;
9111-
JSCompiler_inline_result.flags & 32 &&
9112-
(setTextContent(parent$125, ""),
9113-
(JSCompiler_inline_result.flags &= -33));
9118+
var parent$125 = hostParentFiber.stateNode;
9119+
hostParentFiber.flags & 32 &&
9120+
(setTextContent(parent$125, ""), (hostParentFiber.flags &= -33));
91149121
var before$126 = getHostSibling(finishedWork);
91159122
insertOrAppendPlacementNode(finishedWork, before$126, parent$125);
91169123
break;
91179124
case 3:
91189125
case 4:
9119-
var parent$127 = JSCompiler_inline_result.stateNode.containerInfo,
9126+
var parent$127 = hostParentFiber.stateNode.containerInfo,
91209127
before$128 = getHostSibling(finishedWork);
91219128
insertOrAppendPlacementNodeIntoContainer(
91229129
finishedWork,
@@ -9183,6 +9190,9 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber) {
91839190
null === finishedWork.memoizedState &&
91849191
recursivelyTraverseDisappearLayoutEffects(finishedWork);
91859192
break;
9193+
case 30:
9194+
recursivelyTraverseDisappearLayoutEffects(finishedWork);
9195+
break;
91869196
default:
91879197
recursivelyTraverseDisappearLayoutEffects(finishedWork);
91889198
}
@@ -9289,6 +9299,8 @@ function recursivelyTraverseReappearLayoutEffects(
92899299
);
92909300
safelyAttachRef(finishedWork, finishedWork.return);
92919301
break;
9302+
case 30:
9303+
break;
92929304
default:
92939305
recursivelyTraverseReappearLayoutEffects(
92949306
finishedRoot,
@@ -16177,14 +16189,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) {
1617716189
};
1617816190
var isomorphicReactPackageVersion$jscomp$inline_1817 = React.version;
1617916191
if (
16180-
"19.1.0-native-fb-99e10240-20250310" !==
16192+
"19.1.0-native-fb-6aa8254b-20250312" !==
1618116193
isomorphicReactPackageVersion$jscomp$inline_1817
1618216194
)
1618316195
throw Error(
1618416196
formatProdErrorMessage(
1618516197
527,
1618616198
isomorphicReactPackageVersion$jscomp$inline_1817,
16187-
"19.1.0-native-fb-99e10240-20250310"
16199+
"19.1.0-native-fb-6aa8254b-20250312"
1618816200
)
1618916201
);
1619016202
ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
@@ -16204,24 +16216,24 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
1620416216
null === componentOrElement ? null : componentOrElement.stateNode;
1620516217
return componentOrElement;
1620616218
};
16207-
var internals$jscomp$inline_2290 = {
16219+
var internals$jscomp$inline_2291 = {
1620816220
bundleType: 0,
16209-
version: "19.1.0-native-fb-99e10240-20250310",
16221+
version: "19.1.0-native-fb-6aa8254b-20250312",
1621016222
rendererPackageName: "react-dom",
1621116223
currentDispatcherRef: ReactSharedInternals,
16212-
reconcilerVersion: "19.1.0-native-fb-99e10240-20250310"
16224+
reconcilerVersion: "19.1.0-native-fb-6aa8254b-20250312"
1621316225
};
1621416226
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
16215-
var hook$jscomp$inline_2291 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
16227+
var hook$jscomp$inline_2292 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
1621616228
if (
16217-
!hook$jscomp$inline_2291.isDisabled &&
16218-
hook$jscomp$inline_2291.supportsFiber
16229+
!hook$jscomp$inline_2292.isDisabled &&
16230+
hook$jscomp$inline_2292.supportsFiber
1621916231
)
1622016232
try {
16221-
(rendererID = hook$jscomp$inline_2291.inject(
16222-
internals$jscomp$inline_2290
16233+
(rendererID = hook$jscomp$inline_2292.inject(
16234+
internals$jscomp$inline_2291
1622316235
)),
16224-
(injectedHook = hook$jscomp$inline_2291);
16236+
(injectedHook = hook$jscomp$inline_2292);
1622516237
} catch (err) {}
1622616238
}
1622716239
exports.createRoot = function (container, options) {
@@ -16313,4 +16325,4 @@ exports.hydrateRoot = function (container, initialChildren, options) {
1631316325
listenToAllSupportedEvents(container);
1631416326
return new ReactDOMHydrationRoot(initialChildren);
1631516327
};
16316-
exports.version = "19.1.0-native-fb-99e10240-20250310";
16328+
exports.version = "19.1.0-native-fb-6aa8254b-20250312";

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/react-dom/cjs/ReactDOMClient-profiling.js

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<7c12902a9046831114a3bab20bbecc2a>>
10+
* @generated SignedSource<<fe7dbcd6aec056f9f8ebbeb99b91ad5b>>
1111
*/
1212

1313
/*
@@ -8179,6 +8179,9 @@ function safelyAttachRef(current, nearestMountedAncestor) {
81798179
case 5:
81808180
var instanceToUse = current.stateNode;
81818181
break;
8182+
case 30:
8183+
instanceToUse = current.stateNode;
8184+
break;
81828185
default:
81838186
instanceToUse = current.stateNode;
81848187
}
@@ -8739,6 +8742,8 @@ function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork) {
87398742
? safelyAttachRef(finishedWork, finishedWork.return)
87408743
: safelyDetachRef(finishedWork, finishedWork.return));
87418744
break;
8745+
case 30:
8746+
break;
87428747
default:
87438748
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork);
87448749
}
@@ -9475,6 +9480,7 @@ function commitMutationEffectsOnFiber(finishedWork, root) {
94759480
attachSuspenseRetryListeners(finishedWork, flags)));
94769481
break;
94779482
case 30:
9483+
break;
94789484
case 21:
94799485
break;
94809486
default:
@@ -9486,33 +9492,34 @@ function commitReconciliationEffects(finishedWork) {
94869492
var flags = finishedWork.flags;
94879493
if (flags & 2) {
94889494
try {
9489-
a: {
9490-
for (var parent = finishedWork.return; null !== parent; ) {
9491-
if (isHostParent(parent)) {
9492-
var JSCompiler_inline_result = parent;
9493-
break a;
9494-
}
9495-
parent = parent.return;
9495+
for (
9496+
var hostParentFiber, parentFiber = finishedWork.return;
9497+
null !== parentFiber;
9498+
9499+
) {
9500+
if (isHostParent(parentFiber)) {
9501+
hostParentFiber = parentFiber;
9502+
break;
94969503
}
9497-
throw Error(formatProdErrorMessage(160));
9504+
parentFiber = parentFiber.return;
94989505
}
9499-
switch (JSCompiler_inline_result.tag) {
9506+
if (null == hostParentFiber) throw Error(formatProdErrorMessage(160));
9507+
switch (hostParentFiber.tag) {
95009508
case 27:
9501-
var parent$jscomp$0 = JSCompiler_inline_result.stateNode,
9509+
var parent = hostParentFiber.stateNode,
95029510
before = getHostSibling(finishedWork);
9503-
insertOrAppendPlacementNode(finishedWork, before, parent$jscomp$0);
9511+
insertOrAppendPlacementNode(finishedWork, before, parent);
95049512
break;
95059513
case 5:
9506-
var parent$131 = JSCompiler_inline_result.stateNode;
9507-
JSCompiler_inline_result.flags & 32 &&
9508-
(setTextContent(parent$131, ""),
9509-
(JSCompiler_inline_result.flags &= -33));
9514+
var parent$131 = hostParentFiber.stateNode;
9515+
hostParentFiber.flags & 32 &&
9516+
(setTextContent(parent$131, ""), (hostParentFiber.flags &= -33));
95109517
var before$132 = getHostSibling(finishedWork);
95119518
insertOrAppendPlacementNode(finishedWork, before$132, parent$131);
95129519
break;
95139520
case 3:
95149521
case 4:
9515-
var parent$133 = JSCompiler_inline_result.stateNode.containerInfo,
9522+
var parent$133 = hostParentFiber.stateNode.containerInfo,
95169523
before$134 = getHostSibling(finishedWork);
95179524
insertOrAppendPlacementNodeIntoContainer(
95189525
finishedWork,
@@ -9579,6 +9586,9 @@ function recursivelyTraverseDisappearLayoutEffects(parentFiber) {
95799586
null === finishedWork.memoizedState &&
95809587
recursivelyTraverseDisappearLayoutEffects(finishedWork);
95819588
break;
9589+
case 30:
9590+
recursivelyTraverseDisappearLayoutEffects(finishedWork);
9591+
break;
95829592
default:
95839593
recursivelyTraverseDisappearLayoutEffects(finishedWork);
95849594
}
@@ -9700,6 +9710,8 @@ function recursivelyTraverseReappearLayoutEffects(
97009710
);
97019711
safelyAttachRef(finishedWork, finishedWork.return);
97029712
break;
9713+
case 30:
9714+
break;
97039715
default:
97049716
recursivelyTraverseReappearLayoutEffects(
97059717
finishedRoot,
@@ -16827,14 +16839,14 @@ ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = function (target) {
1682716839
};
1682816840
var isomorphicReactPackageVersion$jscomp$inline_1912 = React.version;
1682916841
if (
16830-
"19.1.0-native-fb-99e10240-20250310" !==
16842+
"19.1.0-native-fb-6aa8254b-20250312" !==
1683116843
isomorphicReactPackageVersion$jscomp$inline_1912
1683216844
)
1683316845
throw Error(
1683416846
formatProdErrorMessage(
1683516847
527,
1683616848
isomorphicReactPackageVersion$jscomp$inline_1912,
16837-
"19.1.0-native-fb-99e10240-20250310"
16849+
"19.1.0-native-fb-6aa8254b-20250312"
1683816850
)
1683916851
);
1684016852
ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
@@ -16856,10 +16868,10 @@ ReactDOMSharedInternals.findDOMNode = function (componentOrElement) {
1685616868
};
1685716869
var internals$jscomp$inline_1919 = {
1685816870
bundleType: 0,
16859-
version: "19.1.0-native-fb-99e10240-20250310",
16871+
version: "19.1.0-native-fb-6aa8254b-20250312",
1686016872
rendererPackageName: "react-dom",
1686116873
currentDispatcherRef: ReactSharedInternals,
16862-
reconcilerVersion: "19.1.0-native-fb-99e10240-20250310",
16874+
reconcilerVersion: "19.1.0-native-fb-6aa8254b-20250312",
1686316875
getLaneLabelMap: function () {
1686416876
for (
1686516877
var map = new Map(), lane = 1, index$294 = 0;
@@ -16877,16 +16889,16 @@ var internals$jscomp$inline_1919 = {
1687716889
}
1687816890
};
1687916891
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
16880-
var hook$jscomp$inline_2350 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
16892+
var hook$jscomp$inline_2351 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
1688116893
if (
16882-
!hook$jscomp$inline_2350.isDisabled &&
16883-
hook$jscomp$inline_2350.supportsFiber
16894+
!hook$jscomp$inline_2351.isDisabled &&
16895+
hook$jscomp$inline_2351.supportsFiber
1688416896
)
1688516897
try {
16886-
(rendererID = hook$jscomp$inline_2350.inject(
16898+
(rendererID = hook$jscomp$inline_2351.inject(
1688716899
internals$jscomp$inline_1919
1688816900
)),
16889-
(injectedHook = hook$jscomp$inline_2350);
16901+
(injectedHook = hook$jscomp$inline_2351);
1689016902
} catch (err) {}
1689116903
}
1689216904
exports.createRoot = function (container, options) {
@@ -16978,4 +16990,4 @@ exports.hydrateRoot = function (container, initialChildren, options) {
1697816990
listenToAllSupportedEvents(container);
1697916991
return new ReactDOMHydrationRoot(initialChildren);
1698016992
};
16981-
exports.version = "19.1.0-native-fb-99e10240-20250310";
16993+
exports.version = "19.1.0-native-fb-6aa8254b-20250312";

0 commit comments

Comments
 (0)