diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index f3872ac483b57..d7cfef9cc7648 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -32,6 +32,7 @@ type Thenable = { type Container = { rootID: string, children: Array, + pendingChildren: Array, }; type Props = {prop: any, hidden: boolean, children?: mixed}; type Instance = {| @@ -457,7 +458,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { finalizeContainerChildren( container: Container, newChildren: Array, - ): void {}, + ): void { + container.pendingChildren = newChildren; + }, replaceContainerChildren( container: Container, @@ -581,13 +584,22 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { } }, + getPendingChildren(rootID: string = DEFAULT_ROOT_ID) { + const container = rootContainers.get(rootID); + if (container) { + return container.pendingChildren; + } else { + return null; + } + }, + getOrCreateRootContainer( rootID: string = DEFAULT_ROOT_ID, isConcurrent: boolean = false, ) { let root = roots.get(rootID); if (!root) { - const container = {rootID: rootID, children: []}; + const container = {rootID: rootID, pendingChildren: [], children: []}; rootContainers.set(rootID, container); root = NoopRenderer.createContainer(container, isConcurrent, false); roots.set(rootID, root); @@ -614,6 +626,25 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { return children; }, + getPendingChildrenAsJSX(rootID: string = DEFAULT_ROOT_ID) { + const children = childToJSX(ReactNoop.getPendingChildren(rootID), null); + if (children === null) { + return null; + } + if (Array.isArray(children)) { + return { + $$typeof: REACT_ELEMENT_TYPE, + type: REACT_FRAGMENT_TYPE, + key: null, + ref: null, + props: {children}, + _owner: null, + _store: __DEV__ ? {} : undefined, + }; + } + return children; + }, + createPortal( children: ReactNodeList, container: Container, @@ -778,7 +809,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { // Trick to flush passive effects without exposing an internal API: // Create a throwaway root and schedule a dummy update on it. const rootID = 'bloopandthenmoreletterstoavoidaconflict'; - const container = {rootID: rootID, children: []}; + const container = {rootID: rootID, pendingChildren: [], children: []}; rootContainers.set(rootID, container); const root = NoopRenderer.createContainer(container, true, false); NoopRenderer.updateContainer(null, root, null, null); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js index abd5fe012e213..f438dbb2dafd4 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js @@ -1387,9 +1387,50 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(ReactNoop.getChildren()).toEqual([span('Hi')]); }); - if (!global.__PERSISTENT__) { - // TODO: Write persistent version of this test - it('toggles visibility during the mutation phase', async () => { + if (global.__PERSISTENT__) { + it('hides/unhides suspended children before layout effects fire (persistent)', async () => { + const {useRef, useLayoutEffect} = React; + + function Parent() { + const child = useRef(null); + + useLayoutEffect(() => { + Scheduler.yieldValue(ReactNoop.getPendingChildrenAsJSX()); + }); + + return ( + + ); + } + + function App(props) { + return ( + }> + + + ); + } + + ReactNoop.renderLegacySyncRoot(); + + expect(Scheduler).toHaveYielded([ + 'Suspend! [Hi]', + 'Loading...', + // The child should have already been hidden + + , + ]); + + await advanceTimers(1000); + + expect(Scheduler).toHaveYielded(['Promise resolved [Hi]', 'Hi']); + }); + } else { + it('hides/unhides suspended children before layout effects fire (mutation)', async () => { const {useRef, useLayoutEffect} = React; function Parent() {