Skip to content

Commit cd6dbf0

Browse files
committed
Allow new children to notify fragment instances in Fabric
1 parent 203df2c commit cd6dbf0

File tree

4 files changed

+103
-26
lines changed

4 files changed

+103
-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
@@ -3045,19 +3045,19 @@ export function updateFragmentInstanceFiber(
30453045
}
30463046
30473047
export function commitNewChildToFragmentInstance(
3048-
childElement: Instance,
3048+
childInstance: Instance,
30493049
fragmentInstance: FragmentInstanceType,
30503050
): void {
30513051
const eventListeners = fragmentInstance._eventListeners;
30523052
if (eventListeners !== null) {
30533053
for (let i = 0; i < eventListeners.length; i++) {
30543054
const {type, listener, optionsOrUseCapture} = eventListeners[i];
3055-
childElement.addEventListener(type, listener, optionsOrUseCapture);
3055+
childInstance.addEventListener(type, listener, optionsOrUseCapture);
30563056
}
30573057
}
30583058
if (fragmentInstance._observers !== null) {
30593059
fragmentInstance._observers.forEach(observer => {
3060-
observer.observe(childElement);
3060+
observer.observe(childInstance);
30613061
});
30623062
}
30633063
}

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: 51 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+
appendImmutableNodeToFragmentInstances(
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,35 @@ function commitPlacement(finishedWork: Fiber): void {
581582
}
582583
}
583584

585+
function appendImmutableNodeToFragmentInstances(
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+
appendImmutableNodeToFragmentInstances(child, parentFragmentInstances);
606+
let sibling = child.sibling;
607+
while (sibling !== null) {
608+
appendImmutableNodeToFragmentInstances(sibling, parentFragmentInstances);
609+
sibling = sibling.sibling;
610+
}
611+
}
612+
}
613+
584614
export function commitHostPlacement(finishedWork: Fiber) {
585615
try {
586616
if (__DEV__) {

0 commit comments

Comments
 (0)