diff --git a/fixtures/unstable-async/suspense/README.md b/fixtures/unstable-async/suspense/README.md index f2a3e3315037f..206227de45827 100644 --- a/fixtures/unstable-async/suspense/README.md +++ b/fixtures/unstable-async/suspense/README.md @@ -14,19 +14,13 @@ No. The APIs being tested here are unstable and some of them have still not been Clone the React repository. -First, open this file locally: - -* `packages/shared/ReactFeatureFlags.js` (make sure you didn't open a similarly named file!) - -Set [the `enableSuspense` flag](https://github.com/facebook/react/blob/d79238f1eeb6634ba7a3df23c3b2709b56cbb8b2/packages/shared/ReactFeatureFlags.js#L19) to `true` and save the file. - -**After you've done that,** follow these steps: +Follow these steps: ```shell # 1: Build react from source cd /path/to/react yarn -yarn build dom-client,core,react-cache,schedule --type=NODE +yarn build dom-client,core,react-cache,scheduler --type=NODE # 2: Install fixture dependencies cd fixtures/unstable-async/suspense/ diff --git a/fixtures/unstable-async/suspense/src/components/App.js b/fixtures/unstable-async/suspense/src/components/App.js index 2f5fa09af0981..eec2611b7af99 100644 --- a/fixtures/unstable-async/suspense/src/components/App.js +++ b/fixtures/unstable-async/suspense/src/components/App.js @@ -1,4 +1,4 @@ -import React, {Placeholder, PureComponent} from 'react'; +import React, {unstable_Suspense as Suspense, PureComponent} from 'react'; import {unstable_scheduleCallback} from 'scheduler'; import { unstable_trace as trace, @@ -76,21 +76,21 @@ export default class App extends PureComponent { }}> Return to list - }> + }> - + ); } renderList(loadingId) { return ( - }> + }> - + ); } } diff --git a/fixtures/unstable-async/suspense/src/components/UserPage.js b/fixtures/unstable-async/suspense/src/components/UserPage.js index e1d30c780da02..6a83fe0a0c890 100644 --- a/fixtures/unstable-async/suspense/src/components/UserPage.js +++ b/fixtures/unstable-async/suspense/src/components/UserPage.js @@ -1,4 +1,4 @@ -import React, {Placeholder} from 'react'; +import React, {unstable_Suspense as Suspense} from 'react'; import {createResource} from 'react-cache'; import Spinner from './Spinner'; import {cache} from '../cache'; @@ -14,9 +14,9 @@ export default function UserPage({id}) { alignItems: 'start', }}> - }> + }> - + ); } @@ -118,7 +118,7 @@ function Img({src, alt, ...rest}) { function UserPicture({source}) { return ( - }> + }> profile picture - + ); } diff --git a/packages/react-dom/src/__tests__/ReactDOMServerPlaceholders-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js similarity index 88% rename from packages/react-dom/src/__tests__/ReactDOMServerPlaceholders-test.internal.js rename to packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js index e1542860f1460..878f0b8727df8 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerPlaceholders-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerSuspense-test.internal.js @@ -21,7 +21,6 @@ function initModules() { jest.resetModuleRegistry(); ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableSuspense = true; ReactFeatureFlags.enableSuspenseServerRenderer = true; React = require('react'); @@ -39,7 +38,7 @@ const {resetModules, serverRender} = ReactDOMServerIntegrationUtils( initModules, ); -describe('ReactDOMServerPlaceholders', () => { +describe('ReactDOMServerSuspense', () => { beforeEach(() => { resetModules(); }); @@ -49,9 +48,9 @@ describe('ReactDOMServerPlaceholders', () => { throw new Promise(() => {}); }; const e = await serverRender( - }> + }> - , + , ); expect(e.tagName).toBe('DIV'); diff --git a/packages/react-dom/src/server/ReactPartialRenderer.js b/packages/react-dom/src/server/ReactPartialRenderer.js index 0b1e720595337..d92f68b5003c5 100644 --- a/packages/react-dom/src/server/ReactPartialRenderer.js +++ b/packages/react-dom/src/server/ReactPartialRenderer.js @@ -33,7 +33,7 @@ import { REACT_FRAGMENT_TYPE, REACT_STRICT_MODE_TYPE, REACT_CONCURRENT_MODE_TYPE, - REACT_PLACEHOLDER_TYPE, + REACT_SUSPENSE_TYPE, REACT_PORTAL_TYPE, REACT_PROFILER_TYPE, REACT_PROVIDER_TYPE, @@ -916,7 +916,7 @@ class ReactDOMServerRenderer { this.stack.push(frame); return ''; } - case REACT_PLACEHOLDER_TYPE: { + case REACT_SUSPENSE_TYPE: { if (enableSuspenseServerRenderer) { const nextChildren = toArray( // Always use the fallback when synchronously rendering to string. diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 470ecd1dd3539..3f508efe63f2d 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -33,7 +33,7 @@ import { ContextProvider, ContextConsumer, Profiler, - PlaceholderComponent, + SuspenseComponent, FunctionComponentLazy, ClassComponentLazy, ForwardRefLazy, @@ -58,7 +58,7 @@ import { REACT_PROVIDER_TYPE, REACT_CONTEXT_TYPE, REACT_CONCURRENT_MODE_TYPE, - REACT_PLACEHOLDER_TYPE, + REACT_SUSPENSE_TYPE, REACT_PURE_TYPE, } from 'shared/ReactSymbols'; @@ -442,8 +442,8 @@ export function createFiberFromElement( break; case REACT_PROFILER_TYPE: return createFiberFromProfiler(pendingProps, mode, expirationTime, key); - case REACT_PLACEHOLDER_TYPE: - fiberTag = PlaceholderComponent; + case REACT_SUSPENSE_TYPE: + fiberTag = SuspenseComponent; break; default: { if (typeof type === 'object' && type !== null) { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 2a9536b399a62..d5b3545622862 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -30,7 +30,7 @@ import { ContextProvider, ContextConsumer, Profiler, - PlaceholderComponent, + SuspenseComponent, PureComponent, PureComponentLazy, } from 'shared/ReactWorkTags'; @@ -45,7 +45,6 @@ import { } from 'shared/ReactSideEffectTags'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import { - enableSuspense, debugRenderPhaseSideEffects, debugRenderPhaseSideEffectsForStrictMode, enableProfilerTimer, @@ -935,77 +934,72 @@ function mountIndeterminateComponent( } } -function updatePlaceholderComponent( +function updateSuspenseComponent( current, workInProgress, renderExpirationTime, ) { - if (enableSuspense) { - const nextProps = workInProgress.pendingProps; - - // Check if we already attempted to render the normal state. If we did, - // and we timed out, render the placeholder state. - const alreadyCaptured = - (workInProgress.effectTag & DidCapture) === NoEffect; - - let nextDidTimeout; - if (current !== null && workInProgress.updateQueue !== null) { - // We're outside strict mode. Something inside this Placeholder boundary - // suspended during the last commit. Switch to the placholder. - workInProgress.updateQueue = null; - nextDidTimeout = true; - } else { - nextDidTimeout = !alreadyCaptured; - } + const nextProps = workInProgress.pendingProps; - if ((workInProgress.mode & StrictMode) !== NoEffect) { - if (nextDidTimeout) { - // If the timed-out view commits, schedule an update effect to record - // the committed time. - workInProgress.effectTag |= Update; - } else { - // The state node points to the time at which placeholder timed out. - // We can clear it once we switch back to the normal children. - workInProgress.stateNode = null; - } - } + // Check if we already attempted to render the normal state. If we did, + // and we timed out, render the placeholder state. + const alreadyCaptured = (workInProgress.effectTag & DidCapture) === NoEffect; - // If the `children` prop is a function, treat it like a render prop. - // TODO: This is temporary until we finalize a lower level API. - const children = nextProps.children; - let nextChildren; - if (typeof children === 'function') { - nextChildren = children(nextDidTimeout); - } else { - nextChildren = nextDidTimeout ? nextProps.fallback : children; - } + let nextDidTimeout; + if (current !== null && workInProgress.updateQueue !== null) { + // We're outside strict mode. Something inside this Placeholder boundary + // suspended during the last commit. Switch to the placholder. + workInProgress.updateQueue = null; + nextDidTimeout = true; + } else { + nextDidTimeout = !alreadyCaptured; + } - if (current !== null && nextDidTimeout !== workInProgress.memoizedState) { - // We're about to switch from the placeholder children to the normal - // children, or vice versa. These are two different conceptual sets that - // happen to be stored in the same set. Call this special function to - // force the new set not to match with the current set. - // TODO: The proper way to model this is by storing each set separately. - forceUnmountCurrentAndReconcile( - current, - workInProgress, - nextChildren, - renderExpirationTime, - ); + if ((workInProgress.mode & StrictMode) !== NoEffect) { + if (nextDidTimeout) { + // If the timed-out view commits, schedule an update effect to record + // the committed time. + workInProgress.effectTag |= Update; } else { - reconcileChildren( - current, - workInProgress, - nextChildren, - renderExpirationTime, - ); + // The state node points to the time at which placeholder timed out. + // We can clear it once we switch back to the normal children. + workInProgress.stateNode = null; } - workInProgress.memoizedProps = nextProps; - workInProgress.memoizedState = nextDidTimeout; - return workInProgress.child; + } + + // If the `children` prop is a function, treat it like a render prop. + // TODO: This is temporary until we finalize a lower level API. + const children = nextProps.children; + let nextChildren; + if (typeof children === 'function') { + nextChildren = children(nextDidTimeout); } else { - return null; + nextChildren = nextDidTimeout ? nextProps.fallback : children; + } + + if (current !== null && nextDidTimeout !== workInProgress.memoizedState) { + // We're about to switch from the placeholder children to the normal + // children, or vice versa. These are two different conceptual sets that + // happen to be stored in the same set. Call this special function to + // force the new set not to match with the current set. + // TODO: The proper way to model this is by storing each set separately. + forceUnmountCurrentAndReconcile( + current, + workInProgress, + nextChildren, + renderExpirationTime, + ); + } else { + reconcileChildren( + current, + workInProgress, + nextChildren, + renderExpirationTime, + ); } + workInProgress.memoizedProps = nextProps; + workInProgress.memoizedState = nextDidTimeout; + return workInProgress.child; } function updatePortalComponent( @@ -1342,8 +1336,8 @@ function beginWork( return updateHostComponent(current, workInProgress, renderExpirationTime); case HostText: return updateHostText(current, workInProgress); - case PlaceholderComponent: - return updatePlaceholderComponent( + case SuspenseComponent: + return updateSuspenseComponent( current, workInProgress, renderExpirationTime, diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 53993f4136aec..52ab8d5a47593 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -22,7 +22,6 @@ import type {CapturedValue, CapturedError} from './ReactCapturedValue'; import { enableSchedulerTracing, enableProfilerTimer, - enableSuspense, } from 'shared/ReactFeatureFlags'; import { ClassComponent, @@ -32,7 +31,7 @@ import { HostText, HostPortal, Profiler, - PlaceholderComponent, + SuspenseComponent, } from 'shared/ReactWorkTags'; import { invokeGuardedCallback, @@ -352,22 +351,20 @@ function commitLifeCycles( } return; } - case PlaceholderComponent: { - if (enableSuspense) { - if ((finishedWork.mode & StrictMode) === NoEffect) { - // In loose mode, a placeholder times out by scheduling a synchronous - // update in the commit phase. Use `updateQueue` field to signal that - // the Timeout needs to switch to the placeholder. We don't need an - // entire queue. Any non-null value works. - // $FlowFixMe - Intentionally using a value other than an UpdateQueue. - finishedWork.updateQueue = emptyObject; - scheduleWork(finishedWork, Sync); - } else { - // In strict mode, the Update effect is used to record the time at - // which the placeholder timed out. - const currentTime = requestCurrentTime(); - finishedWork.stateNode = {timedOutAt: currentTime}; - } + case SuspenseComponent: { + if ((finishedWork.mode & StrictMode) === NoEffect) { + // In loose mode, a placeholder times out by scheduling a synchronous + // update in the commit phase. Use `updateQueue` field to signal that + // the Timeout needs to switch to the placeholder. We don't need an + // entire queue. Any non-null value works. + // $FlowFixMe - Intentionally using a value other than an UpdateQueue. + finishedWork.updateQueue = emptyObject; + scheduleWork(finishedWork, Sync); + } else { + // In strict mode, the Update effect is used to record the time at + // which the placeholder timed out. + const currentTime = requestCurrentTime(); + finishedWork.stateNode = {timedOutAt: currentTime}; } return; } @@ -863,7 +860,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { case Profiler: { return; } - case PlaceholderComponent: { + case SuspenseComponent: { return; } default: { diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 8eac4863863b2..c0b53764a5694 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -34,7 +34,7 @@ import { Fragment, Mode, Profiler, - PlaceholderComponent, + SuspenseComponent, ForwardRefLazy, PureComponent, PureComponentLazy, @@ -508,7 +508,7 @@ function completeWork( case ForwardRef: case ForwardRefLazy: break; - case PlaceholderComponent: + case SuspenseComponent: break; case Fragment: break; diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 9660c38a5b831..5b0353679b95e 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -49,7 +49,6 @@ import { enableUserTimingAPI, replayFailedUnitOfWorkWithInvokeGuardedCallback, warnAboutDeprecatedLifecycles, - enableSuspense, } from 'shared/ReactFeatureFlags'; import getComponentName from 'shared/getComponentName'; import invariant from 'shared/invariant'; @@ -1374,7 +1373,7 @@ function renderRoot( } } - if (enableSuspense && !isExpired && nextLatestAbsoluteTimeoutMs !== -1) { + if (!isExpired && nextLatestAbsoluteTimeoutMs !== -1) { // The tree was suspended. const suspendedExpirationTime = expirationTime; markSuspendedPriorityLevel(root, suspendedExpirationTime); @@ -1567,42 +1566,40 @@ function retrySuspendedRoot( fiber: Fiber, suspendedTime: ExpirationTime, ) { - if (enableSuspense) { - let retryTime; + let retryTime; - if (isPriorityLevelSuspended(root, suspendedTime)) { - // Ping at the original level - retryTime = suspendedTime; + if (isPriorityLevelSuspended(root, suspendedTime)) { + // Ping at the original level + retryTime = suspendedTime; - markPingedPriorityLevel(root, retryTime); - } else { - // Placeholder already timed out. Compute a new expiration time - const currentTime = requestCurrentTime(); - retryTime = computeExpirationForFiber(currentTime, fiber); - markPendingPriorityLevel(root, retryTime); - } - - // TODO: If the placeholder fiber has already rendered the primary children - // without suspending (that is, all of the promises have already resolved), - // we should not trigger another update here. One case this happens is when - // we are in sync mode and a single promise is thrown both on initial render - // and on update; we attach two .then(retrySuspendedRoot) callbacks and each - // one performs Sync work, rerendering the Placeholder. - - if ((fiber.mode & ConcurrentMode) !== NoContext) { - if (root === nextRoot && nextRenderExpirationTime === suspendedTime) { - // Received a ping at the same priority level at which we're currently - // rendering. Restart from the root. - nextRoot = null; - } - } + markPingedPriorityLevel(root, retryTime); + } else { + // Suspense already timed out. Compute a new expiration time + const currentTime = requestCurrentTime(); + retryTime = computeExpirationForFiber(currentTime, fiber); + markPendingPriorityLevel(root, retryTime); + } - scheduleWorkToRoot(fiber, retryTime); - const rootExpirationTime = root.expirationTime; - if (rootExpirationTime !== NoWork) { - requestWork(root, rootExpirationTime); + // TODO: If the placeholder fiber has already rendered the primary children + // without suspending (that is, all of the promises have already resolved), + // we should not trigger another update here. One case this happens is when + // we are in sync mode and a single promise is thrown both on initial render + // and on update; we attach two .then(retrySuspendedRoot) callbacks and each + // one performs Sync work, rerendering the Suspense. + + if ((fiber.mode & ConcurrentMode) !== NoContext) { + if (root === nextRoot && nextRenderExpirationTime === suspendedTime) { + // Received a ping at the same priority level at which we're currently + // rendering. Restart from the root. + nextRoot = null; } } + + scheduleWorkToRoot(fiber, retryTime); + const rootExpirationTime = root.expirationTime; + if (rootExpirationTime !== NoWork) { + requestWork(root, rootExpirationTime); + } } function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null { @@ -1877,7 +1874,7 @@ function onSuspend( msUntilTimeout: number, ): void { root.expirationTime = rootExpirationTime; - if (enableSuspense && msUntilTimeout === 0 && !shouldYield()) { + if (msUntilTimeout === 0 && !shouldYield()) { // Don't wait an additional tick. Commit the tree immediately. root.pendingCommitExpirationTime = suspendedExpirationTime; root.finishedWork = finishedWork; @@ -1895,17 +1892,15 @@ function onYield(root) { } function onTimeout(root, finishedWork, suspendedExpirationTime) { - if (enableSuspense) { - // The root timed out. Commit it. - root.pendingCommitExpirationTime = suspendedExpirationTime; - root.finishedWork = finishedWork; - // Read the current time before entering the commit phase. We can be - // certain this won't cause tearing related to batching of event updates - // because we're at the top of a timer event. - recomputeCurrentRendererTime(); - currentSchedulerTime = currentRendererTime; - flushRoot(root, suspendedExpirationTime); - } + // The root timed out. Commit it. + root.pendingCommitExpirationTime = suspendedExpirationTime; + root.finishedWork = finishedWork; + // Read the current time before entering the commit phase. We can be + // certain this won't cause tearing related to batching of event updates + // because we're at the top of a timer event. + recomputeCurrentRendererTime(); + currentSchedulerTime = currentRendererTime; + flushRoot(root, suspendedExpirationTime); } function onCommit(root, expirationTime) { @@ -2247,7 +2242,7 @@ function performWorkOnRoot( // If this root previously suspended, clear its existing timeout, since // we're about to try rendering again. const timeoutHandle = root.timeoutHandle; - if (enableSuspense && timeoutHandle !== noTimeout) { + if (timeoutHandle !== noTimeout) { root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above cancelTimeout(timeoutHandle); @@ -2271,7 +2266,7 @@ function performWorkOnRoot( // If this root previously suspended, clear its existing timeout, since // we're about to try rendering again. const timeoutHandle = root.timeoutHandle; - if (enableSuspense && timeoutHandle !== noTimeout) { + if (timeoutHandle !== noTimeout) { root.timeoutHandle = noTimeout; // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above cancelTimeout(timeoutHandle); diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index a77213a62a6b2..7918f64e6c696 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -26,7 +26,7 @@ import { HostComponent, HostPortal, ContextProvider, - PlaceholderComponent, + SuspenseComponent, } from 'shared/ReactWorkTags'; import { DidCapture, @@ -36,7 +36,7 @@ import { Update as UpdateEffect, LifecycleEffectMask, } from 'shared/ReactSideEffectTags'; -import {enableSuspense, enableSchedulerTracing} from 'shared/ReactFeatureFlags'; +import {enableSchedulerTracing} from 'shared/ReactFeatureFlags'; import {StrictMode, ConcurrentMode} from './ReactTypeOfMode'; import {createCapturedValue} from './ReactCapturedValue'; @@ -158,7 +158,6 @@ function throwException( sourceFiber.firstEffect = sourceFiber.lastEffect = null; if ( - enableSuspense && value !== null && typeof value === 'object' && typeof value.then === 'function' @@ -175,7 +174,7 @@ function throwException( let earliestTimeoutMs = -1; let startTimeMs = -1; do { - if (workInProgress.tag === PlaceholderComponent) { + if (workInProgress.tag === SuspenseComponent) { const current = workInProgress.alternate; if ( current !== null && @@ -193,7 +192,7 @@ function throwException( // Do not search any further. break; } - let timeoutPropMs = workInProgress.pendingProps.delayMs; + let timeoutPropMs = workInProgress.pendingProps.maxDuration; if (typeof timeoutPropMs === 'number') { if (timeoutPropMs <= 0) { earliestTimeoutMs = 0; @@ -208,10 +207,10 @@ function throwException( workInProgress = workInProgress.return; } while (workInProgress !== null); - // Schedule the nearest Placeholder to re-render the timed out view. + // Schedule the nearest Suspense to re-render the timed out view. workInProgress = returnFiber; do { - if (workInProgress.tag === PlaceholderComponent) { + if (workInProgress.tag === SuspenseComponent) { const didTimeout = workInProgress.memoizedState; if (!didTimeout) { // Found the nearest boundary. @@ -238,10 +237,10 @@ function throwException( // If the boundary is outside of strict mode, we should *not* suspend // the commit. Pretend as if the suspended component rendered null and // keep rendering. In the commit phase, we'll schedule a subsequent - // synchronous update to re-render the Placeholder. + // synchronous update to re-render the Suspense. // // Note: It doesn't matter whether the component that suspended was - // inside a strict mode tree. If the Placeholder is outside of it, we + // inside a strict mode tree. If the Suspense is outside of it, we // should *not* suspend the commit. if ((workInProgress.mode & StrictMode) === NoEffect) { workInProgress.effectTag |= UpdateEffect; @@ -434,7 +433,7 @@ function unwindWork( popHostContext(workInProgress); return null; } - case PlaceholderComponent: { + case SuspenseComponent: { const effectTag = workInProgress.effectTag; if (effectTag & ShouldCapture) { workInProgress.effectTag = (effectTag & ~ShouldCapture) | DidCapture; diff --git a/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js b/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js index 1d0ffb2c20df0..d5ea81a2f5e34 100644 --- a/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactPure-test.internal.js @@ -21,7 +21,6 @@ describe('pure', () => { jest.resetModules(); ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; - ReactFeatureFlags.enableSuspense = true; React = require('react'); ReactNoop = require('react-noop-renderer'); }); @@ -50,7 +49,7 @@ describe('pure', () => { function sharedTests(label, pure) { describe(`${label}`, () => { it('bails out on props equality', async () => { - const {Placeholder} = React; + const {unstable_Suspense: Suspense} = React; function Counter({count}) { return ; @@ -58,9 +57,9 @@ describe('pure', () => { Counter = pure(Counter); ReactNoop.render( - + - , + , ); expect(ReactNoop.flush()).toEqual([]); await Promise.resolve(); @@ -69,18 +68,18 @@ describe('pure', () => { // Should bail out because props have not changed ReactNoop.render( - + - , + , ); expect(ReactNoop.flush()).toEqual([]); expect(ReactNoop.getChildren()).toEqual([span(0)]); // Should update because count prop changed ReactNoop.render( - + - , + , ); expect(ReactNoop.flush()).toEqual([1]); expect(ReactNoop.getChildren()).toEqual([span(1)]); @@ -88,7 +87,7 @@ describe('pure', () => { }); it("does not bail out if there's a context change", async () => { - const {Placeholder} = React; + const {unstable_Suspense: Suspense} = React; const CountContext = React.createContext(0); @@ -102,11 +101,11 @@ describe('pure', () => { state = {count: 0}; render() { return ( - + - + ); } } @@ -130,7 +129,7 @@ describe('pure', () => { }); it('accepts custom comparison function', async () => { - const {Placeholder} = React; + const {unstable_Suspense: Suspense} = React; function Counter({count}) { return ; @@ -143,9 +142,9 @@ describe('pure', () => { }); ReactNoop.render( - + - , + , ); expect(ReactNoop.flush()).toEqual([]); await Promise.resolve(); @@ -154,18 +153,18 @@ describe('pure', () => { // Should bail out because props have not changed ReactNoop.render( - + - , + , ); expect(ReactNoop.flush()).toEqual(['Old count: 0, New count: 0']); expect(ReactNoop.getChildren()).toEqual([span(0)]); // Should update because count prop changed ReactNoop.render( - + - , + , ); expect(ReactNoop.flush()).toEqual(['Old count: 0, New count: 1', 1]); expect(ReactNoop.getChildren()).toEqual([span(1)]); diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js index 80bd4efc756c3..a19ee4617843d 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js @@ -2,7 +2,7 @@ let React; let ReactTestRenderer; let ReactFeatureFlags; let ReactCache; -let Placeholder; +let Suspense; // let JestReact; @@ -18,13 +18,12 @@ describe('ReactSuspense', () => { ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; - ReactFeatureFlags.enableSuspense = true; React = require('react'); ReactTestRenderer = require('react-test-renderer'); // JestReact = require('jest-react'); ReactCache = require('react-cache'); - Placeholder = React.Placeholder; + Suspense = React.unstable_Suspense; function invalidateCache() { cache = ReactCache.createCache(invalidateCache); @@ -107,12 +106,12 @@ describe('ReactSuspense', () => { function Foo() { ReactTestRenderer.unstable_yield('Foo'); return ( - + - + ); } @@ -145,15 +144,15 @@ describe('ReactSuspense', () => { }); it('suspends siblings and later recovers each independently', () => { - // Render two sibling Placeholder components + // Render two sibling Suspense components const root = ReactTestRenderer.create( - }> + }> - - }> + + }> - + , { unstable_isConcurrent: true, @@ -173,8 +172,8 @@ describe('ReactSuspense', () => { expect(root).toFlushWithoutYielding(); expect(root).toMatchRenderedOutput('Loading A...Loading B...'); - // Advance time by enough that the first Placeholder's promise resolves and - // switches back to the normal view. The second Placeholder should still + // Advance time by enough that the first Suspense's promise resolves and + // switches back to the normal view. The second Suspense should still // show the placeholder jest.advanceTimersByTime(1000); // TODO: Should we throw if you forget to call toHaveYielded? @@ -182,7 +181,7 @@ describe('ReactSuspense', () => { expect(root).toFlushAndYield(['A']); expect(root).toMatchRenderedOutput('ALoading B...'); - // Advance time by enough that the second Placeholder's promise resolves + // Advance time by enough that the second Suspense's promise resolves // and switches back to the normal view jest.advanceTimersByTime(1000); expect(ReactTestRenderer).toHaveYielded(['Promise resolved [B]']); @@ -219,10 +218,10 @@ describe('ReactSuspense', () => { } const root = ReactTestRenderer.create( - }> + }> - , + , { unstable_isConcurrent: true, }, diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js index 6259d798f09b6..dd224fb5c6d29 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js @@ -3,7 +3,7 @@ let ReactFeatureFlags; let Fragment; let ReactNoop; let ReactCache; -let Placeholder; +let Suspense; let StrictMode; let ConcurrentMode; let lazy; @@ -21,12 +21,11 @@ describe('ReactSuspenseWithNoopRenderer', () => { ReactFeatureFlags = require('shared/ReactFeatureFlags'); ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; - ReactFeatureFlags.enableSuspense = true; React = require('react'); Fragment = React.Fragment; ReactNoop = require('react-noop-renderer'); ReactCache = require('react-cache'); - Placeholder = React.Placeholder; + Suspense = React.unstable_Suspense; StrictMode = React.StrictMode; ConcurrentMode = React.unstable_ConcurrentMode; lazy = React.lazy; @@ -103,12 +102,12 @@ describe('ReactSuspenseWithNoopRenderer', () => { function Foo() { ReactNoop.yield('Foo'); return ( - + - + ); } @@ -143,15 +142,15 @@ describe('ReactSuspenseWithNoopRenderer', () => { }); it('suspends siblings and later recovers each independently', async () => { - // Render two sibling Placeholder components + // Render two sibling Suspense components ReactNoop.render( - }> + }> - - }> + + }> - + , ); expect(ReactNoop.flush()).toEqual([ @@ -172,8 +171,8 @@ describe('ReactSuspenseWithNoopRenderer', () => { span('Loading B...'), ]); - // Advance time by enough that the first Placeholder's promise resolves and - // switches back to the normal view. The second Placeholder should still + // Advance time by enough that the first Suspense's promise resolves and + // switches back to the normal view. The second Suspense should still // show the placeholder ReactNoop.expire(1000); await advanceTimers(1000); @@ -181,7 +180,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(ReactNoop.flush()).toEqual(['Promise resolved [A]', 'A']); expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading B...')]); - // Advance time by enough that the second Placeholder's promise resolves + // Advance time by enough that the second Suspense's promise resolves // and switches back to the normal view ReactNoop.expire(1000); await advanceTimers(1000); @@ -192,12 +191,12 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('continues rendering siblings after suspending', async () => { ReactNoop.render( - + - , + , ); // B suspends. Continue rendering the remaining siblings. expect(ReactNoop.flush()).toEqual(['A', 'Suspend! [B]', 'C', 'D']); @@ -242,11 +241,11 @@ describe('ReactSuspenseWithNoopRenderer', () => { const errorBoundary = React.createRef(); function App() { return ( - + - + ); } @@ -304,11 +303,11 @@ describe('ReactSuspenseWithNoopRenderer', () => { const errorBoundary = React.createRef(); function App() { return ( - }> + }> - + ); } @@ -355,10 +354,10 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('can update at a higher priority while in a suspended state', async () => { function App(props) { return ( - + - + ); } @@ -394,10 +393,10 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('keeps working on lower priority work after being pinged', async () => { function App(props) { return ( - + {props.showB && } - + ); } @@ -422,9 +421,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { return ; } return ( - + - + ); } @@ -449,9 +448,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('forces an expiration after an update times out', async () => { ReactNoop.render( - }> + }> - + , ); @@ -487,14 +486,16 @@ describe('ReactSuspenseWithNoopRenderer', () => { ReactNoop.render( - }> + }> - }> - - + + , ); @@ -568,9 +569,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { ReactNoop.flushSync(() => ReactNoop.render( - }> + }> - + , ), @@ -596,12 +597,12 @@ describe('ReactSuspenseWithNoopRenderer', () => { ReactNoop.flushSync(() => ReactNoop.render( - }> - }> + }> + }> - + - + , ), ); @@ -615,12 +616,12 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(ReactNoop.getChildren()).toEqual([span('Loading (outer)...')]); }); - it('expires early with a `delayMs` option', async () => { + it('expires early with a `maxDuration` option', async () => { ReactNoop.render( - }> + }> - + , ); @@ -651,9 +652,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('resolves successfully even if fallback render is pending', async () => { ReactNoop.render( - }> + }> - , + , ); expect(ReactNoop.flushNextYield()).toEqual(['Suspend! [Async]']); await advanceTimers(1500); @@ -669,18 +670,18 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(() => { ReactNoop.flushSync(() => ReactNoop.render( - {() => }, + {() => }, ), ); }).toThrow('An update was suspended, but no placeholder UI was provided.'); }); - it('a Placeholder component correctly handles more than one suspended child', async () => { + it('a Suspense component correctly handles more than one suspended child', async () => { ReactNoop.render( - + - , + , ); expect(ReactNoop.expire(10000)).toEqual(['Suspend! [A]', 'Suspend! [B]']); expect(ReactNoop.getChildren()).toEqual([]); @@ -698,9 +699,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('can resume rendering earlier than a timeout', async () => { ReactNoop.render( - }> + }> - , + , ); expect(ReactNoop.flush()).toEqual(['Suspend! [Async]', 'Loading...']); expect(ReactNoop.getChildren()).toEqual([]); @@ -723,13 +724,13 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('starts working on an update even if its priority falls between two suspended levels', async () => { function App(props) { return ( - + {props.text === 'C' ? ( ) : ( )} - + ); } @@ -772,7 +773,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { it('can hide a tree to unblock its surroundings', async () => { function App() { return ( - + {didTimeout => (