[Fiber] Extract Fragment instance commit helpers into their own module (#37167)
Just a small refactor to consolidate FragmentInstance helpers into one module
Jack Pope committed
Aug 11, 2026 at 18:46 UTC
22e4f993c7ce9373c95aef093b003f84249e2045
3 files changed
+130
-101
packages/react-reconciler/src/ReactFiberCommitHostEffects.js
+10
-100
@@ -26,7 +26,6 @@ import {
26
HostText,
27
HostPortal,
28
DehydratedFragment,
29
- Fragment,
29
} from './ReactWorkTags';
30
import {ContentReset, Placement} from './ReactFiberFlags';
31
import {
@@ -57,17 +56,16 @@ import {
56
acquireSingletonInstance,
57
releaseSingletonInstance,
58
isSingletonScope,
60
- commitNewChildToFragmentInstance,
61
- deleteChildFromFragmentInstance,
59
} from './ReactFiberConfig';
60
import {captureCommitPhaseError} from './ReactFiberWorkLoop';
61
import {trackHostMutation} from './ReactFiberMutationTracking';
62
63
import {runWithFiberInDEV} from './ReactCurrentFiber';
64
+import {enableFragmentRefs} from 'shared/ReactFeatureFlags';
65
import {
68
- enableFragmentRefs,
69
- enableFragmentRefsTextNodes,
70
-} from 'shared/ReactFeatureFlags';
66
+ commitNewChildToFragmentInstances,
67
+ getParentFragmentInstances,
68
+} from './ReactFiberFragmentInstance';
69
70
export function commitHostMount(finishedWork: Fiber) {
71
const type = finishedWork.type;
@@ -256,58 +254,6 @@ export function commitShowHideHostTextInstance(node: Fiber, isHidden: boolean) {
254
}
255
}
256
259
-export function commitNewChildToFragmentInstances(
260
- fiber: Fiber,
261
- parentFragmentInstances: null | Array<FragmentInstanceType>,
262
-): void {
263
- if (
264
- (fiber.tag !== HostComponent &&
265
- fiber.tag !== HostSingleton &&
266
- !(enableFragmentRefsTextNodes && fiber.tag === HostText)) ||
267
- // Only run fragment insertion effects for initial insertions
268
- fiber.alternate !== null ||
269
- parentFragmentInstances === null
270
- ) {
271
- return;
272
- }
273
- for (let i = 0; i < parentFragmentInstances.length; i++) {
274
- const fragmentInstance = parentFragmentInstances[i];
275
- commitNewChildToFragmentInstance(fiber.stateNode, fragmentInstance);
276
- }
277
-}
278
-
279
-export function commitFragmentInstanceInsertionEffects(fiber: Fiber): void {
280
- let parent = fiber.return;
281
- while (parent !== null) {
282
- if (isFragmentInstanceParent(parent)) {
283
- const fragmentInstance: FragmentInstanceType = parent.stateNode;
284
- commitNewChildToFragmentInstance(fiber.stateNode, fragmentInstance);
285
- }
286
-
287
- if (isFragmentInstanceHostBoundary(parent)) {
288
- return;
289
- }
290
-
291
- parent = parent.return;
292
- }
293
-}
294
-
295
-export function commitFragmentInstanceDeletionEffects(fiber: Fiber): void {
296
- let parent = fiber.return;
297
- while (parent !== null) {
298
- if (isFragmentInstanceParent(parent)) {
299
- const fragmentInstance: FragmentInstanceType = parent.stateNode;
300
- deleteChildFromFragmentInstance(fiber.stateNode, fragmentInstance);
301
- }
302
-
303
- if (isFragmentInstanceHostBoundary(parent)) {
304
- return;
305
- }
306
-
307
- parent = parent.return;
308
- }
309
-}
310
-
257
function isHostParent(fiber: Fiber): boolean {
258
return (
259
fiber.tag === HostComponent ||
@@ -322,24 +268,6 @@ function isHostParent(fiber: Fiber): boolean {
268
);
269
}
270
325
-// HostPortal / HostHoistable are host parents for placement, but not for
326
-// fragment instance ancestry — commit bookkeeping walks past them so it
327
-// matches getFragmentParentInstanceOrContainerFiber. HostSingleton is a
328
-// fragment host boundary (and a collected child) even when it is not a
329
-// placement scope.
330
-function isFragmentInstanceHostBoundary(fiber: Fiber): boolean {
331
- return (
332
- fiber.tag === HostComponent ||
333
- fiber.tag === HostRoot ||
334
- // $FlowFixMe[constant-condition]
335
- (supportsSingletons ? fiber.tag === HostSingleton : false)
336
- );
337
-}
338
-
339
-function isFragmentInstanceParent(fiber: Fiber): boolean {
340
- return fiber && fiber.tag === Fragment && fiber.stateNode !== null;
341
-}
342
-
271
function getHostSibling(fiber: Fiber): ?Instance {
272
// We're going to search forward into the tree until we find a sibling host
273
// node. Unfortunately, if multiple insertions are done in a row we have to
@@ -524,37 +452,19 @@ function insertOrAppendPlacementNode(
452
function commitPlacement(finishedWork: Fiber): void {
453
// Recursively insert all host nodes into the parent.
454
let hostParentFiber;
527
- let parentFragmentInstances = null;
528
- let collectFragmentInstances = enableFragmentRefs;
455
let parentFiber = finishedWork.return;
456
while (parentFiber !== null) {
531
- if (collectFragmentInstances && isFragmentInstanceParent(parentFiber)) {
532
- const fragmentInstance: FragmentInstanceType = parentFiber.stateNode;
533
- if (parentFragmentInstances === null) {
534
- parentFragmentInstances = [fragmentInstance];
535
- } else {
536
- parentFragmentInstances.push(fragmentInstance);
537
- }
538
- }
539
- if (hostParentFiber === undefined && isHostParent(parentFiber)) {
540
- // Nearest host parent for placement. Portals still win here so
541
- // children insert into the portal container.
457
+ if (isHostParent(parentFiber)) {
458
hostParentFiber = parentFiber;
543
- }
544
- if (
545
- collectFragmentInstances &&
546
- isFragmentInstanceHostBoundary(parentFiber)
547
- ) {
548
- // Stop collecting at HostComponent / HostRoot / HostSingleton.
549
- // Placement can continue past non-scope singletons to HostRoot, and
550
- // past portals (already recorded above) to fragment ancestors.
551
- collectFragmentInstances = false;
552
- }
553
- if (hostParentFiber !== undefined && !collectFragmentInstances) {
459
break;
460
}
461
parentFiber = parentFiber.return;
462
}
463
+ // Fragment ancestry is collected separately so portals remain placement
464
+ // parents while fragment bookkeeping still walks past them to ancestors.
465
+ const parentFragmentInstances = enableFragmentRefs
466
+ ? getParentFragmentInstances(finishedWork)
467
+ : null;
468
469
// $FlowFixMe[constant-condition]
470
if (!supportsMutation) {
packages/react-reconciler/src/ReactFiberCommitWork.js
+3
-1
@@ -255,9 +255,11 @@ import {
255
commitHostRemoveChild,
256
commitHostSingletonAcquisition,
257
commitHostSingletonRelease,
258
+} from './ReactFiberCommitHostEffects';
259
+import {
260
commitFragmentInstanceDeletionEffects,
261
commitFragmentInstanceInsertionEffects,
260
-} from './ReactFiberCommitHostEffects';
262
+} from './ReactFiberFragmentInstance';
263
import {
264
trackEnterViewTransitions,
265
commitEnterViewTransitions,
packages/react-reconciler/src/ReactFiberFragmentInstance.js
new
+117
@@ -0,0 +1,117 @@
1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @flow
8
+ */
9
+
10
+import type {FragmentInstanceType} from './ReactFiberConfig';
11
+import type {Fiber} from './ReactInternalTypes';
12
+
13
+import {
14
+ HostRoot,
15
+ HostComponent,
16
+ HostSingleton,
17
+ HostText,
18
+ Fragment,
19
+} from './ReactWorkTags';
20
+import {
21
+ supportsSingletons,
22
+ commitNewChildToFragmentInstance,
23
+ deleteChildFromFragmentInstance,
24
+} from './ReactFiberConfig';
25
+import {enableFragmentRefsTextNodes} from 'shared/ReactFeatureFlags';
26
+
27
+export function commitNewChildToFragmentInstances(
28
+ fiber: Fiber,
29
+ parentFragmentInstances: null | Array<FragmentInstanceType>,
30
+): void {
31
+ if (
32
+ (fiber.tag !== HostComponent &&
33
+ fiber.tag !== HostSingleton &&
34
+ !(enableFragmentRefsTextNodes && fiber.tag === HostText)) ||
35
+ // Only run fragment insertion effects for initial insertions
36
+ fiber.alternate !== null ||
37
+ parentFragmentInstances === null
38
+ ) {
39
+ return;
40
+ }
41
+ for (let i = 0; i < parentFragmentInstances.length; i++) {
42
+ const fragmentInstance = parentFragmentInstances[i];
43
+ commitNewChildToFragmentInstance(fiber.stateNode, fragmentInstance);
44
+ }
45
+}
46
+
47
+export function commitFragmentInstanceInsertionEffects(fiber: Fiber): void {
48
+ let parent = fiber.return;
49
+ while (parent !== null) {
50
+ if (isFragmentInstanceParent(parent)) {
51
+ const fragmentInstance: FragmentInstanceType = parent.stateNode;
52
+ commitNewChildToFragmentInstance(fiber.stateNode, fragmentInstance);
53
+ }
54
+
55
+ if (isFragmentInstanceHostBoundary(parent)) {
56
+ return;
57
+ }
58
+
59
+ parent = parent.return;
60
+ }
61
+}
62
+
63
+export function commitFragmentInstanceDeletionEffects(fiber: Fiber): void {
64
+ let parent = fiber.return;
65
+ while (parent !== null) {
66
+ if (isFragmentInstanceParent(parent)) {
67
+ const fragmentInstance: FragmentInstanceType = parent.stateNode;
68
+ deleteChildFromFragmentInstance(fiber.stateNode, fragmentInstance);
69
+ }
70
+
71
+ if (isFragmentInstanceHostBoundary(parent)) {
72
+ return;
73
+ }
74
+
75
+ parent = parent.return;
76
+ }
77
+}
78
+
79
+export function getParentFragmentInstances(
80
+ fiber: Fiber,
81
+): null | Array<FragmentInstanceType> {
82
+ let parentFragmentInstances = null;
83
+ let parent = fiber.return;
84
+ while (parent !== null) {
85
+ if (isFragmentInstanceParent(parent)) {
86
+ const fragmentInstance: FragmentInstanceType = parent.stateNode;
87
+ if (parentFragmentInstances === null) {
88
+ parentFragmentInstances = [fragmentInstance];
89
+ } else {
90
+ parentFragmentInstances.push(fragmentInstance);
91
+ }
92
+ }
93
+ if (isFragmentInstanceHostBoundary(parent)) {
94
+ break;
95
+ }
96
+ parent = parent.return;
97
+ }
98
+ return parentFragmentInstances;
99
+}
100
+
101
+// HostPortal / HostHoistable are host parents for placement, but not for
102
+// fragment instance ancestry — commit bookkeeping walks past them so it
103
+// matches getFragmentParentInstanceOrContainerFiber. HostSingleton is a
104
+// fragment host boundary (and a collected child) even when it is not a
105
+// placement scope.
106
+function isFragmentInstanceHostBoundary(fiber: Fiber): boolean {
107
+ return (
108
+ fiber.tag === HostComponent ||
109
+ fiber.tag === HostRoot ||
110
+ // $FlowFixMe[constant-condition]
111
+ (supportsSingletons ? fiber.tag === HostSingleton : false)
112
+ );
113
+}
114
+
115
+function isFragmentInstanceParent(fiber: Fiber): boolean {
116
+ return fiber && fiber.tag === Fragment && fiber.stateNode !== null;
117
+}