Skip to content

Commit

Permalink
Enable Suspense + rename Placeholder (facebook#13799)
Browse files Browse the repository at this point in the history
* Enable Suspense

* <unstable_Placeholder delayMs> => <unstable_Suspense maxDuration>

* Update suspense fixture
  • Loading branch information
gaearon authored and jetoneza committed Jan 23, 2019
1 parent c747739 commit 543e36e
Show file tree
Hide file tree
Showing 30 changed files with 326 additions and 362 deletions.
10 changes: 2 additions & 8 deletions fixtures/unstable-async/suspense/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
10 changes: 5 additions & 5 deletions fixtures/unstable-async/suspense/src/components/App.js
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -76,21 +76,21 @@ export default class App extends PureComponent {
}}>
Return to list
</button>
<Placeholder delayMs={2000} fallback={<Spinner size="large" />}>
<Suspense maxDuration={2000} fallback={<Spinner size="large" />}>
<UserPageLoader id={id} />
</Placeholder>
</Suspense>
</div>
);
}

renderList(loadingId) {
return (
<Placeholder delayMs={1500} fallback={<Spinner size="large" />}>
<Suspense maxDuration={1500} fallback={<Spinner size="large" />}>
<ContributorListPage
loadingId={loadingId}
onUserClick={this.handleUserClick}
/>
</Placeholder>
</Suspense>
);
}
}
10 changes: 5 additions & 5 deletions fixtures/unstable-async/suspense/src/components/UserPage.js
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,9 +14,9 @@ export default function UserPage({id}) {
alignItems: 'start',
}}>
<UserDetails id={id} />
<Placeholder delayMs={1000} fallback={<Spinner size="medium" />}>
<Suspense maxDuration={1000} fallback={<Spinner size="medium" />}>
<Repositories id={id} />
</Placeholder>
</Suspense>
</div>
);
}
Expand Down Expand Up @@ -118,7 +118,7 @@ function Img({src, alt, ...rest}) {

function UserPicture({source}) {
return (
<Placeholder delayMs={1500} fallback={<img src={source} alt="poster" />}>
<Suspense maxDuration={1500} fallback={<img src={source} alt="poster" />}>
<Img
src={source}
alt="profile picture"
Expand All @@ -128,7 +128,7 @@ function UserPicture({source}) {
borderRadius: '0.5rem',
}}
/>
</Placeholder>
</Suspense>
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ function initModules() {
jest.resetModuleRegistry();

ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactFeatureFlags.enableSuspense = true;
ReactFeatureFlags.enableSuspenseServerRenderer = true;

React = require('react');
Expand All @@ -39,7 +38,7 @@ const {resetModules, serverRender} = ReactDOMServerIntegrationUtils(
initModules,
);

describe('ReactDOMServerPlaceholders', () => {
describe('ReactDOMServerSuspense', () => {
beforeEach(() => {
resetModules();
});
Expand All @@ -49,9 +48,9 @@ describe('ReactDOMServerPlaceholders', () => {
throw new Promise(() => {});
};
const e = await serverRender(
<React.Placeholder fallback={<div />}>
<React.unstable_Suspense fallback={<div />}>
<Suspended />
</React.Placeholder>,
</React.unstable_Suspense>,
);

expect(e.tagName).toBe('DIV');
Expand Down
4 changes: 2 additions & 2 deletions packages/react-dom/src/server/ReactPartialRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
8 changes: 4 additions & 4 deletions packages/react-reconciler/src/ReactFiber.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
ContextProvider,
ContextConsumer,
Profiler,
PlaceholderComponent,
SuspenseComponent,
FunctionComponentLazy,
ClassComponentLazy,
ForwardRefLazy,
Expand All @@ -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';

Expand Down Expand Up @@ -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) {
Expand Down
122 changes: 58 additions & 64 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
ContextProvider,
ContextConsumer,
Profiler,
PlaceholderComponent,
SuspenseComponent,
PureComponent,
PureComponentLazy,
} from 'shared/ReactWorkTags';
Expand All @@ -45,7 +45,6 @@ import {
} from 'shared/ReactSideEffectTags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
enableSuspense,
debugRenderPhaseSideEffects,
debugRenderPhaseSideEffectsForStrictMode,
enableProfilerTimer,
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down
35 changes: 16 additions & 19 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import type {CapturedValue, CapturedError} from './ReactCapturedValue';
import {
enableSchedulerTracing,
enableProfilerTimer,
enableSuspense,
} from 'shared/ReactFeatureFlags';
import {
ClassComponent,
Expand All @@ -32,7 +31,7 @@ import {
HostText,
HostPortal,
Profiler,
PlaceholderComponent,
SuspenseComponent,
} from 'shared/ReactWorkTags';
import {
invokeGuardedCallback,
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -863,7 +860,7 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
case Profiler: {
return;
}
case PlaceholderComponent: {
case SuspenseComponent: {
return;
}
default: {
Expand Down

0 comments on commit 543e36e

Please sign in to comment.