diff --git a/packages/react-native-renderer/src/ReactFabricHostConfig.js b/packages/react-native-renderer/src/ReactFabricHostConfig.js index 842a33291fb07..39058e9ac5a31 100644 --- a/packages/react-native-renderer/src/ReactFabricHostConfig.js +++ b/packages/react-native-renderer/src/ReactFabricHostConfig.js @@ -7,6 +7,7 @@ * @flow */ +import type {ReactNodeList, OffscreenMode} from 'shared/ReactTypes'; import type {ElementRef} from 'react'; import type { HostComponent, @@ -301,6 +302,9 @@ export function getChildHostContext( type === 'RCTText' || type === 'RCTVirtualText'; + // TODO: If this is an offscreen host container, we should reuse the + // parent context. + if (prevIsInAParentText !== isInAParentText) { return {isInAParentText}; } else { @@ -413,30 +417,32 @@ export function cloneInstance( }; } -export function cloneHiddenInstance( - instance: Instance, - type: string, - props: Props, - internalInstanceHandle: Object, -): Instance { - const viewConfig = instance.canonical.viewConfig; - const node = instance.node; - const updatePayload = create( - {style: {display: 'none'}}, - viewConfig.validAttributes, - ); - return { - node: cloneNodeWithNewProps(node, updatePayload), - canonical: instance.canonical, - }; +// TODO: These two methods should be replaced with `createOffscreenInstance` and +// `cloneOffscreenInstance`. I did it this way for now because the offscreen +// instance is stored on an extra HostComponent fiber instead of the +// OffscreenComponent fiber, and I didn't want to add an extra check to the +// generic HostComponent path. Instead we should use the OffscreenComponent +// fiber, but currently Fabric expects a 1:1 correspondence between Fabric +// instances and host fibers, so I'm leaving this optimization for later once +// we can confirm this won't break any downstream expectations. +export function getOffscreenContainerType(): string { + return 'RCTView'; } -export function cloneHiddenTextInstance( - instance: Instance, - text: string, - internalInstanceHandle: Object, -): TextInstance { - throw new Error('Not yet implemented.'); +export function getOffscreenContainerProps( + mode: OffscreenMode, + children: ReactNodeList, +): Props { + if (mode === 'hidden') { + return { + children, + style: {display: 'none'}, + }; + } else { + return { + children, + }; + } } export function createContainerChildSet(container: Container): ChildSet { diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 652f32521c249..f6af7341c2e0c 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -16,7 +16,7 @@ import type {Fiber} from 'react-reconciler/src/ReactInternalTypes'; import type {UpdateQueue} from 'react-reconciler/src/ReactUpdateQueue'; -import type {ReactNodeList} from 'shared/ReactTypes'; +import type {ReactNodeList, OffscreenMode} from 'shared/ReactTypes'; import type {RootTag} from 'react-reconciler/src/ReactRootTags'; import * as Scheduler from 'scheduler/unstable_mock'; @@ -258,6 +258,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { type: string, rootcontainerInstance: Container, ) { + if (type === 'offscreen') { + return parentHostContext; + } if (type === 'uppercase') { return UPPERCASE_CONTEXT; } @@ -539,47 +542,18 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { container.children = newChildren; }, - cloneHiddenInstance( - instance: Instance, - type: string, - props: Props, - internalInstanceHandle: Object, - ): Instance { - const clone = cloneInstance( - instance, - null, - type, - props, - props, - internalInstanceHandle, - true, - null, - ); - clone.hidden = true; - return clone; + getOffscreenContainerType(): string { + return 'offscreen'; }, - cloneHiddenTextInstance( - instance: TextInstance, - text: string, - internalInstanceHandle: Object, - ): TextInstance { - const clone = { - text: instance.text, - id: instanceCounter++, - hidden: true, - context: instance.context, + getOffscreenContainerProps( + mode: OffscreenMode, + children: ReactNodeList, + ): Props { + return { + hidden: mode === 'hidden', + children, }; - // Hide from unit tests - Object.defineProperty(clone, 'id', { - value: clone.id, - enumerable: false, - }); - Object.defineProperty(clone, 'context', { - value: clone.context, - enumerable: false, - }); - return clone; }, }; @@ -646,7 +620,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { function getChildren(root) { if (root) { - return root.children; + return useMutation + ? root.children + : removeOffscreenContainersFromChildren(root.children, false); } else { return null; } @@ -654,12 +630,154 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { function getPendingChildren(root) { if (root) { - return root.pendingChildren; + return useMutation + ? root.children + : removeOffscreenContainersFromChildren(root.pendingChildren, false); } else { return null; } } + function removeOffscreenContainersFromChildren(children, hideNearestNode) { + // Mutation mode and persistent mode have different outputs for Offscreen + // and Suspense trees. Persistent mode adds an additional host node wrapper, + // whereas mutation mode does not. + // + // This function removes the offscreen host wrappers so that the output is + // consistent. If the offscreen node is hidden, it transfers the hiddenness + // to the child nodes, to mimic how it works in mutation mode. That way our + // tests don't have to fork tree assertions. + // + // So, it takes a tree that looks like this: + // + //