Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Suspense + rename Placeholder #13799

Merged
merged 3 commits into from
Oct 10, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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