Skip to content

Commit 1835b3f

Browse files
authored
New children notify fragment instances in Fabric (facebook#33093)
When a new child of a fragment instance is inserted, we need to notify the instance to keep any relevant tracking up to date. For example, we automatically observe the new child with any active IntersectionObserver. For mutable renderers (DOM), we reuse the existing traversal in `commitPlacement` that does the insertions for HostComponents. Immutable renderers (Fabric) exit this path before the traversal though, so currently we can't notify the fragment instances. Here I've created a separate traversal in `commitPlacement`, specifically for immutable renders when `enableFragmentRefs` is on.
1 parent f4041aa commit 1835b3f

File tree

4 files changed

+109
-26
lines changed

4 files changed

+109
-26
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3073,19 +3073,19 @@ export function updateFragmentInstanceFiber(
30733073
}
30743074
30753075
export function commitNewChildToFragmentInstance(
3076-
childElement: Instance,
3076+
childInstance: Instance,
30773077
fragmentInstance: FragmentInstanceType,
30783078
): void {
30793079
const eventListeners = fragmentInstance._eventListeners;
30803080
if (eventListeners !== null) {
30813081
for (let i = 0; i < eventListeners.length; i++) {
30823082
const {type, listener, optionsOrUseCapture} = eventListeners[i];
3083-
childElement.addEventListener(type, listener, optionsOrUseCapture);
3083+
childInstance.addEventListener(type, listener, optionsOrUseCapture);
30843084
}
30853085
}
30863086
if (fragmentInstance._observers !== null) {
30873087
fragmentInstance._observers.forEach(observer => {
3088-
observer.observe(childElement);
3088+
observer.observe(childInstance);
30893089
});
30903090
}
30913091
}

packages/react-native-renderer/src/ReactFiberConfigFabric.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -695,12 +695,17 @@ export function updateFragmentInstanceFiber(
695695
}
696696

697697
export function commitNewChildToFragmentInstance(
698-
child: Fiber,
698+
childInstance: Instance,
699699
fragmentInstance: FragmentInstanceType,
700700
): void {
701+
const publicInstance = getPublicInstance(childInstance);
701702
if (fragmentInstance._observers !== null) {
703+
if (publicInstance == null) {
704+
throw new Error('Expected to find a host node. This is a bug in React.');
705+
}
702706
fragmentInstance._observers.forEach(observer => {
703-
observeChild(child, observer);
707+
// $FlowFixMe[incompatible-call] Element types are behind a flag in RN
708+
observer.observe(publicInstance);
704709
});
705710
}
706711
}

packages/react-native-renderer/src/__tests__/ReactFabricFragmentRefs-test.internal.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,46 @@ describe('Fabric FragmentRefs', () => {
8080

8181
expect(fragmentRef && fragmentRef._fragmentFiber).toBeTruthy();
8282
});
83+
84+
describe('observers', () => {
85+
// @gate enableFragmentRefs
86+
it('observes children, newly added children', async () => {
87+
let logs = [];
88+
const observer = {
89+
observe: entry => {
90+
// Here we reference internals because we don't need to mock the native observer
91+
// We only need to test that each child node is observed on insertion
92+
logs.push(entry.__internalInstanceHandle.pendingProps.nativeID);
93+
},
94+
};
95+
function Test({showB}) {
96+
const fragmentRef = React.useRef(null);
97+
React.useEffect(() => {
98+
fragmentRef.current.observeUsing(observer);
99+
const lastRefValue = fragmentRef.current;
100+
return () => {
101+
lastRefValue.unobserveUsing(observer);
102+
};
103+
}, []);
104+
return (
105+
<View nativeID="parent">
106+
<React.Fragment ref={fragmentRef}>
107+
<View nativeID="A" />
108+
{showB && <View nativeID="B" />}
109+
</React.Fragment>
110+
</View>
111+
);
112+
}
113+
114+
await act(() => {
115+
ReactFabric.render(<Test showB={false} />, 11, null, true);
116+
});
117+
expect(logs).toEqual(['A']);
118+
logs = [];
119+
await act(() => {
120+
ReactFabric.render(<Test showB={true} />, 11, null, true);
121+
});
122+
expect(logs).toEqual(['B']);
123+
});
124+
});
83125
});

packages/react-reconciler/src/ReactFiberCommitHostEffects.js

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,16 @@ export function commitShowHideHostTextInstance(node: Fiber, isHidden: boolean) {
255255

256256
export function commitNewChildToFragmentInstances(
257257
fiber: Fiber,
258-
parentFragmentInstances: Array<FragmentInstanceType>,
258+
parentFragmentInstances: null | Array<FragmentInstanceType>,
259259
): void {
260+
if (
261+
fiber.tag !== HostComponent ||
262+
// Only run fragment insertion effects for initial insertions
263+
fiber.alternate !== null ||
264+
parentFragmentInstances === null
265+
) {
266+
return;
267+
}
260268
for (let i = 0; i < parentFragmentInstances.length; i++) {
261269
const fragmentInstance = parentFragmentInstances[i];
262270
commitNewChildToFragmentInstance(fiber.stateNode, fragmentInstance);
@@ -384,14 +392,7 @@ function insertOrAppendPlacementNodeIntoContainer(
384392
} else {
385393
appendChildToContainer(parent, stateNode);
386394
}
387-
// TODO: Enable HostText for RN
388-
if (
389-
enableFragmentRefs &&
390-
tag === HostComponent &&
391-
// Only run fragment insertion effects for initial insertions
392-
node.alternate === null &&
393-
parentFragmentInstances !== null
394-
) {
395+
if (enableFragmentRefs) {
395396
commitNewChildToFragmentInstances(node, parentFragmentInstances);
396397
}
397398
trackHostMutation();
@@ -449,14 +450,7 @@ function insertOrAppendPlacementNode(
449450
} else {
450451
appendChild(parent, stateNode);
451452
}
452-
// TODO: Enable HostText for RN
453-
if (
454-
enableFragmentRefs &&
455-
tag === HostComponent &&
456-
// Only run fragment insertion effects for initial insertions
457-
node.alternate === null &&
458-
parentFragmentInstances !== null
459-
) {
453+
if (enableFragmentRefs) {
460454
commitNewChildToFragmentInstances(node, parentFragmentInstances);
461455
}
462456
trackHostMutation();
@@ -494,10 +488,6 @@ function insertOrAppendPlacementNode(
494488
}
495489

496490
function commitPlacement(finishedWork: Fiber): void {
497-
if (!supportsMutation) {
498-
return;
499-
}
500-
501491
// Recursively insert all host nodes into the parent.
502492
let hostParentFiber;
503493
let parentFragmentInstances = null;
@@ -517,6 +507,17 @@ function commitPlacement(finishedWork: Fiber): void {
517507
}
518508
parentFiber = parentFiber.return;
519509
}
510+
511+
if (!supportsMutation) {
512+
if (enableFragmentRefs) {
513+
commitImmutablePlacementNodeToFragmentInstances(
514+
finishedWork,
515+
parentFragmentInstances,
516+
);
517+
}
518+
return;
519+
}
520+
520521
if (hostParentFiber == null) {
521522
throw new Error(
522523
'Expected to find a host parent. This error is likely caused by a bug ' +
@@ -581,6 +582,41 @@ function commitPlacement(finishedWork: Fiber): void {
581582
}
582583
}
583584

585+
function commitImmutablePlacementNodeToFragmentInstances(
586+
finishedWork: Fiber,
587+
parentFragmentInstances: null | Array<FragmentInstanceType>,
588+
): void {
589+
if (!enableFragmentRefs) {
590+
return;
591+
}
592+
const isHost = finishedWork.tag === HostComponent;
593+
if (isHost) {
594+
commitNewChildToFragmentInstances(finishedWork, parentFragmentInstances);
595+
return;
596+
} else if (finishedWork.tag === HostPortal) {
597+
// If the insertion itself is a portal, then we don't want to traverse
598+
// down its children. Instead, we'll get insertions from each child in
599+
// the portal directly.
600+
return;
601+
}
602+
603+
const child = finishedWork.child;
604+
if (child !== null) {
605+
commitImmutablePlacementNodeToFragmentInstances(
606+
child,
607+
parentFragmentInstances,
608+
);
609+
let sibling = child.sibling;
610+
while (sibling !== null) {
611+
commitImmutablePlacementNodeToFragmentInstances(
612+
sibling,
613+
parentFragmentInstances,
614+
);
615+
sibling = sibling.sibling;
616+
}
617+
}
618+
}
619+
584620
export function commitHostPlacement(finishedWork: Fiber) {
585621
try {
586622
if (__DEV__) {

0 commit comments

Comments
 (0)