Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit c28f313

Browse filesBrowse files
authored
experimental_use(promise) for SSR (#25214)
Follow up to #25084 and #25207. Implements experimental_use(promise) API in the SSR runtime (Fizz). This is largely a copy-paste of the Flight implementation. I have intentionally tried to keep both as close as possible.
1 parent d6f9628 commit c28f313
Copy full SHA for c28f313

6 files changed

+525-34Lines changed: 525 additions & 34 deletions

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js‎

Copy file name to clipboardExpand all lines: packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+212Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ let Suspense;
1919
let SuspenseList;
2020
let useSyncExternalStore;
2121
let useSyncExternalStoreWithSelector;
22+
let use;
2223
let PropTypes;
2324
let textCache;
2425
let window;
@@ -42,6 +43,7 @@ describe('ReactDOMFizzServer', () => {
4243
Suspense = React.Suspense;
4344
if (gate(flags => flags.enableSuspenseList)) {
4445
SuspenseList = React.SuspenseList;
46+
use = React.experimental_use;
4547
}
4648

4749
PropTypes = require('prop-types');
@@ -5243,5 +5245,215 @@ describe('ReactDOMFizzServer', () => {
52435245
console.error = originalConsoleError;
52445246
}
52455247
});
5248+
5249+
// @gate enableUseHook
5250+
it('basic use(promise)', async () => {
5251+
const promiseA = Promise.resolve('A');
5252+
const promiseB = Promise.resolve('B');
5253+
const promiseC = Promise.resolve('C');
5254+
5255+
function Async() {
5256+
return use(promiseA) + use(promiseB) + use(promiseC);
5257+
}
5258+
5259+
function App() {
5260+
return (
5261+
<Suspense fallback="Loading...">
5262+
<Async />
5263+
</Suspense>
5264+
);
5265+
}
5266+
5267+
await act(async () => {
5268+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5269+
pipe(writable);
5270+
});
5271+
5272+
// TODO: The `act` implementation in this file doesn't unwrap microtasks
5273+
// automatically. We can't use the same `act` we use for Fiber tests
5274+
// because that relies on the mock Scheduler. Doesn't affect any public
5275+
// API but we might want to fix this for our own internal tests.
5276+
//
5277+
// For now, wait for each promise in sequence.
5278+
await act(async () => {
5279+
await promiseA;
5280+
});
5281+
await act(async () => {
5282+
await promiseB;
5283+
});
5284+
await act(async () => {
5285+
await promiseC;
5286+
});
5287+
5288+
expect(getVisibleChildren(container)).toEqual('ABC');
5289+
5290+
ReactDOMClient.hydrateRoot(container, <App />);
5291+
expect(Scheduler).toFlushAndYield([]);
5292+
expect(getVisibleChildren(container)).toEqual('ABC');
5293+
});
5294+
5295+
// @gate enableUseHook
5296+
it('use(promise) in multiple components', async () => {
5297+
const promiseA = Promise.resolve('A');
5298+
const promiseB = Promise.resolve('B');
5299+
const promiseC = Promise.resolve('C');
5300+
const promiseD = Promise.resolve('D');
5301+
5302+
function Child({prefix}) {
5303+
return prefix + use(promiseC) + use(promiseD);
5304+
}
5305+
5306+
function Parent() {
5307+
return <Child prefix={use(promiseA) + use(promiseB)} />;
5308+
}
5309+
5310+
function App() {
5311+
return (
5312+
<Suspense fallback="Loading...">
5313+
<Parent />
5314+
</Suspense>
5315+
);
5316+
}
5317+
5318+
await act(async () => {
5319+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5320+
pipe(writable);
5321+
});
5322+
5323+
// TODO: The `act` implementation in this file doesn't unwrap microtasks
5324+
// automatically. We can't use the same `act` we use for Fiber tests
5325+
// because that relies on the mock Scheduler. Doesn't affect any public
5326+
// API but we might want to fix this for our own internal tests.
5327+
//
5328+
// For now, wait for each promise in sequence.
5329+
await act(async () => {
5330+
await promiseA;
5331+
});
5332+
await act(async () => {
5333+
await promiseB;
5334+
});
5335+
await act(async () => {
5336+
await promiseC;
5337+
});
5338+
await act(async () => {
5339+
await promiseD;
5340+
});
5341+
5342+
expect(getVisibleChildren(container)).toEqual('ABCD');
5343+
5344+
ReactDOMClient.hydrateRoot(container, <App />);
5345+
expect(Scheduler).toFlushAndYield([]);
5346+
expect(getVisibleChildren(container)).toEqual('ABCD');
5347+
});
5348+
5349+
// @gate enableUseHook
5350+
it('using a rejected promise will throw', async () => {
5351+
const promiseA = Promise.resolve('A');
5352+
const promiseB = Promise.reject(new Error('Oops!'));
5353+
const promiseC = Promise.resolve('C');
5354+
5355+
// Jest/Node will raise an unhandled rejected error unless we await this. It
5356+
// works fine in the browser, though.
5357+
await expect(promiseB).rejects.toThrow('Oops!');
5358+
5359+
function Async() {
5360+
return use(promiseA) + use(promiseB) + use(promiseC);
5361+
}
5362+
5363+
class ErrorBoundary extends React.Component {
5364+
state = {error: null};
5365+
static getDerivedStateFromError(error) {
5366+
return {error};
5367+
}
5368+
render() {
5369+
if (this.state.error) {
5370+
return this.state.error.message;
5371+
}
5372+
return this.props.children;
5373+
}
5374+
}
5375+
5376+
function App() {
5377+
return (
5378+
<Suspense fallback="Loading...">
5379+
<ErrorBoundary>
5380+
<Async />
5381+
</ErrorBoundary>
5382+
</Suspense>
5383+
);
5384+
}
5385+
5386+
const reportedServerErrors = [];
5387+
await act(async () => {
5388+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />, {
5389+
onError(error) {
5390+
reportedServerErrors.push(error);
5391+
},
5392+
});
5393+
pipe(writable);
5394+
});
5395+
5396+
// TODO: The `act` implementation in this file doesn't unwrap microtasks
5397+
// automatically. We can't use the same `act` we use for Fiber tests
5398+
// because that relies on the mock Scheduler. Doesn't affect any public
5399+
// API but we might want to fix this for our own internal tests.
5400+
//
5401+
// For now, wait for each promise in sequence.
5402+
await act(async () => {
5403+
await promiseA;
5404+
});
5405+
await act(async () => {
5406+
await expect(promiseB).rejects.toThrow('Oops!');
5407+
});
5408+
await act(async () => {
5409+
await promiseC;
5410+
});
5411+
5412+
expect(getVisibleChildren(container)).toEqual('Loading...');
5413+
expect(reportedServerErrors.length).toBe(1);
5414+
expect(reportedServerErrors[0].message).toBe('Oops!');
5415+
5416+
const reportedClientErrors = [];
5417+
ReactDOMClient.hydrateRoot(container, <App />, {
5418+
onRecoverableError(error) {
5419+
reportedClientErrors.push(error);
5420+
},
5421+
});
5422+
expect(Scheduler).toFlushAndYield([]);
5423+
expect(getVisibleChildren(container)).toEqual('Oops!');
5424+
expect(reportedClientErrors.length).toBe(1);
5425+
if (__DEV__) {
5426+
expect(reportedClientErrors[0].message).toBe('Oops!');
5427+
} else {
5428+
expect(reportedClientErrors[0].message).toBe(
5429+
'The server could not finish this Suspense boundary, likely due to ' +
5430+
'an error during server rendering. Switched to client rendering.',
5431+
);
5432+
}
5433+
});
5434+
5435+
// @gate enableUseHook
5436+
it("use a promise that's already been instrumented and resolved", async () => {
5437+
const thenable = {
5438+
status: 'fulfilled',
5439+
value: 'Hi',
5440+
then() {},
5441+
};
5442+
5443+
// This will never suspend because the thenable already resolved
5444+
function App() {
5445+
return use(thenable);
5446+
}
5447+
5448+
await act(async () => {
5449+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
5450+
pipe(writable);
5451+
});
5452+
expect(getVisibleChildren(container)).toEqual('Hi');
5453+
5454+
ReactDOMClient.hydrateRoot(container, <App />);
5455+
expect(Scheduler).toFlushAndYield([]);
5456+
expect(getVisibleChildren(container)).toEqual('Hi');
5457+
});
52465458
});
52475459
});

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.