Add React.startTransition (#19696)

* Add React.startTransition

* Export startTransition from index.js as well
This commit is contained in:
Ricky
2020-08-26 11:34:17 -04:00
committed by GitHub
parent c4e0768d74
commit 5564f2c95b
7 changed files with 242 additions and 0 deletions
@@ -2514,6 +2514,215 @@ describe('ReactSuspenseWithNoopRenderer', () => {
});
});
describe('delays transitions when using React.startTranistion', () => {
// @gate experimental
it('top level render', async () => {
function App({page}) {
return (
<Suspense fallback={<Text text="Loading..." />}>
<AsyncText text={page} ms={5000} />
</Suspense>
);
}
// Initial render.
React.unstable_startTransition(() => ReactNoop.render(<App page="A" />));
expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
// Only a short time is needed to unsuspend the initial loading state.
Scheduler.unstable_advanceTime(400);
await advanceTimers(400);
expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
// Later we load the data.
Scheduler.unstable_advanceTime(5000);
await advanceTimers(5000);
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
expect(Scheduler).toFlushAndYield(['A']);
expect(ReactNoop.getChildren()).toEqual([span('A')]);
// Start transition.
React.unstable_startTransition(() => ReactNoop.render(<App page="B" />));
expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
Scheduler.unstable_advanceTime(2999);
await advanceTimers(2999);
// Since the timeout is infinite (or effectively infinite),
// we have still not yet flushed the loading state.
expect(ReactNoop.getChildren()).toEqual([span('A')]);
// Later we load the data.
Scheduler.unstable_advanceTime(3000);
await advanceTimers(3000);
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
expect(Scheduler).toFlushAndYield(['B']);
expect(ReactNoop.getChildren()).toEqual([span('B')]);
// Start a long (infinite) transition.
React.unstable_startTransition(() => ReactNoop.render(<App page="C" />));
expect(Scheduler).toFlushAndYield(['Suspend! [C]', 'Loading...']);
// Advance past the current (effectively) infinite timeout.
// This is enforcing temporary behavior until it's truly infinite.
Scheduler.unstable_advanceTime(100000);
await advanceTimers(100000);
expect(ReactNoop.getChildren()).toEqual([
hiddenSpan('B'),
span('Loading...'),
]);
});
// @gate experimental
it('hooks', async () => {
let transitionToPage;
function App() {
const [page, setPage] = React.useState('none');
transitionToPage = setPage;
if (page === 'none') {
return null;
}
return (
<Suspense fallback={<Text text="Loading..." />}>
<AsyncText text={page} ms={5000} />
</Suspense>
);
}
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield([]);
// Initial render.
await ReactNoop.act(async () => {
React.unstable_startTransition(() => transitionToPage('A'));
expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
// Only a short time is needed to unsuspend the initial loading state.
Scheduler.unstable_advanceTime(400);
await advanceTimers(400);
expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
});
// Later we load the data.
Scheduler.unstable_advanceTime(5000);
await advanceTimers(5000);
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
expect(Scheduler).toFlushAndYield(['A']);
expect(ReactNoop.getChildren()).toEqual([span('A')]);
// Start transition.
await ReactNoop.act(async () => {
React.unstable_startTransition(() => transitionToPage('B'));
expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
Scheduler.unstable_advanceTime(2999);
await advanceTimers(2999);
// Since the timeout is infinite (or effectively infinite),
// we have still not yet flushed the loading state.
expect(ReactNoop.getChildren()).toEqual([span('A')]);
});
// Later we load the data.
Scheduler.unstable_advanceTime(3000);
await advanceTimers(3000);
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
expect(Scheduler).toFlushAndYield(['B']);
expect(ReactNoop.getChildren()).toEqual([span('B')]);
// Start a long (infinite) transition.
await ReactNoop.act(async () => {
React.unstable_startTransition(() => transitionToPage('C'));
expect(Scheduler).toFlushAndYield(['Suspend! [C]', 'Loading...']);
// Advance past the current effectively infinite timeout.
// This is enforcing temporary behavior until it's truly infinite.
Scheduler.unstable_advanceTime(100000);
await advanceTimers(100000);
expect(ReactNoop.getChildren()).toEqual([
hiddenSpan('B'),
span('Loading...'),
]);
});
});
// @gate experimental
it('classes', async () => {
let transitionToPage;
class App extends React.Component {
state = {page: 'none'};
render() {
transitionToPage = page => this.setState({page});
const page = this.state.page;
if (page === 'none') {
return null;
}
return (
<Suspense fallback={<Text text="Loading..." />}>
<AsyncText text={page} ms={5000} />
</Suspense>
);
}
}
ReactNoop.render(<App />);
expect(Scheduler).toFlushAndYield([]);
// Initial render.
await ReactNoop.act(async () => {
React.unstable_startTransition(() => transitionToPage('A'));
expect(Scheduler).toFlushAndYield(['Suspend! [A]', 'Loading...']);
// Only a short time is needed to unsuspend the initial loading state.
Scheduler.unstable_advanceTime(400);
await advanceTimers(400);
expect(ReactNoop.getChildren()).toEqual([span('Loading...')]);
});
// Later we load the data.
Scheduler.unstable_advanceTime(5000);
await advanceTimers(5000);
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
expect(Scheduler).toFlushAndYield(['A']);
expect(ReactNoop.getChildren()).toEqual([span('A')]);
// Start transition.
await ReactNoop.act(async () => {
React.unstable_startTransition(() => transitionToPage('B'));
expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
Scheduler.unstable_advanceTime(2999);
await advanceTimers(2999);
// Since the timeout is infinite (or effectively infinite),
// we have still not yet flushed the loading state.
expect(ReactNoop.getChildren()).toEqual([span('A')]);
});
// Later we load the data.
Scheduler.unstable_advanceTime(3000);
await advanceTimers(3000);
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
expect(Scheduler).toFlushAndYield(['B']);
expect(ReactNoop.getChildren()).toEqual([span('B')]);
// Start a long (infinite) transition.
await ReactNoop.act(async () => {
React.unstable_startTransition(() => transitionToPage('C'));
expect(Scheduler).toFlushAndYield(['Suspend! [C]', 'Loading...']);
// Advance past the current effectively infinite timeout.
// This is enforcing temporary behavior until it's truly infinite.
Scheduler.unstable_advanceTime(100000);
await advanceTimers(100000);
expect(ReactNoop.getChildren()).toEqual([
hiddenSpan('B'),
span('Loading...'),
]);
});
});
});
// @gate experimental
it('disables suspense config when nothing is passed to withSuspenseConfig', async () => {
function App({page}) {
+2
View File
@@ -46,6 +46,8 @@ export {
useTransition as unstable_useTransition,
useDeferredValue,
useDeferredValue as unstable_useDeferredValue,
startTransition,
startTransition as unstable_startTransition,
SuspenseList,
SuspenseList as unstable_SuspenseList,
unstable_withSuspenseConfig,
+1
View File
@@ -42,6 +42,7 @@ export {
// exposeConcurrentModeAPIs
useTransition as unstable_useTransition,
useDeferredValue as unstable_useDeferredValue,
startTransition as unstable_startTransition,
SuspenseList as unstable_SuspenseList,
unstable_withSuspenseConfig,
// enableBlocksAPI
+2
View File
@@ -72,6 +72,8 @@ export {
createFactory,
useTransition,
useTransition as unstable_useTransition,
startTransition,
startTransition as unstable_startTransition,
useDeferredValue,
useDeferredValue as unstable_useDeferredValue,
SuspenseList,
+2
View File
@@ -45,6 +45,8 @@ export {
useTransition as unstable_useTransition,
useDeferredValue,
useDeferredValue as unstable_useDeferredValue,
startTransition,
startTransition as unstable_startTransition,
SuspenseList,
SuspenseList as unstable_SuspenseList,
unstable_withSuspenseConfig,
+2
View File
@@ -58,6 +58,7 @@ import {
import {createMutableSource} from './ReactMutableSource';
import ReactSharedInternals from './ReactSharedInternals';
import {createFundamental} from './ReactFundamental';
import {startTransition} from './ReactStartTransition';
// TODO: Move this branching into the other module instead and just re-export.
const createElement = __DEV__ ? createElementWithValidation : createElementProd;
@@ -107,6 +108,7 @@ export {
createFactory,
// Concurrent Mode
useTransition,
startTransition,
useDeferredValue,
REACT_SUSPENSE_LIST_TYPE as SuspenseList,
REACT_LEGACY_HIDDEN_TYPE as unstable_LegacyHidden,
@@ -0,0 +1,24 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import ReactCurrentBatchConfig from './ReactCurrentBatchConfig';
// Default to an arbitrarily large timeout. Effectively, this is infinite. The
// eventual goal is to never timeout when refreshing already visible content.
const IndefiniteTimeoutConfig = {timeoutMs: 100000};
export function startTransition(scope: () => void) {
const previousConfig = ReactCurrentBatchConfig.suspense;
ReactCurrentBatchConfig.suspense = IndefiniteTimeoutConfig;
try {
scope();
} finally {
ReactCurrentBatchConfig.suspense = previousConfig;
}
}