Skip to content

Commit d66b192

Browse files
committed
Revert "Revert "experimental_use(promise) for SSR (facebook#25214)""
This reverts commit dedfeff. This reverts commit c28f313. Follow up to facebook#25084 and facebook#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 df3fdd1 commit d66b192

File tree

4 files changed

+415
-30
lines changed

4 files changed

+415
-30
lines changed

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

+212
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ let Suspense;
2525
let SuspenseList;
2626
let useSyncExternalStore;
2727
let useSyncExternalStoreWithSelector;
28+
let use;
2829
let PropTypes;
2930
let textCache;
3031
let window;
@@ -47,6 +48,7 @@ describe('ReactDOMFizzServer', () => {
4748
ReactDOMFizzServer = require('react-dom/server');
4849
Stream = require('stream');
4950
Suspense = React.Suspense;
51+
use = React.use;
5052
if (gate(flags => flags.enableSuspenseList)) {
5153
SuspenseList = React.SuspenseList;
5254
}
@@ -5166,6 +5168,216 @@ describe('ReactDOMFizzServer', () => {
51665168
console.error = originalConsoleError;
51675169
}
51685170
});
5171+
5172+
// @gate enableUseHook
5173+
it('basic use(promise)', async () => {
5174+
const promiseA = Promise.resolve('A');
5175+
const promiseB = Promise.resolve('B');
5176+
const promiseC = Promise.resolve('C');
5177+
5178+
function Async() {
5179+
return use(promiseA) + use(promiseB) + use(promiseC);
5180+
}
5181+
5182+
function App() {
5183+
return (
5184+
<Suspense fallback="Loading...">
5185+
<Async />
5186+
</Suspense>
5187+
);
5188+
}
5189+
5190+
await act(async () => {
5191+
const {pipe} = renderToPipeableStream(<App />);
5192+
pipe(writable);
5193+
});
5194+
5195+
// TODO: The `act` implementation in this file doesn't unwrap microtasks
5196+
// automatically. We can't use the same `act` we use for Fiber tests
5197+
// because that relies on the mock Scheduler. Doesn't affect any public
5198+
// API but we might want to fix this for our own internal tests.
5199+
//
5200+
// For now, wait for each promise in sequence.
5201+
await act(async () => {
5202+
await promiseA;
5203+
});
5204+
await act(async () => {
5205+
await promiseB;
5206+
});
5207+
await act(async () => {
5208+
await promiseC;
5209+
});
5210+
5211+
expect(getVisibleChildren(container)).toEqual('ABC');
5212+
5213+
ReactDOMClient.hydrateRoot(container, <App />);
5214+
expect(Scheduler).toFlushAndYield([]);
5215+
expect(getVisibleChildren(container)).toEqual('ABC');
5216+
});
5217+
5218+
// @gate enableUseHook
5219+
it('use(promise) in multiple components', async () => {
5220+
const promiseA = Promise.resolve('A');
5221+
const promiseB = Promise.resolve('B');
5222+
const promiseC = Promise.resolve('C');
5223+
const promiseD = Promise.resolve('D');
5224+
5225+
function Child({prefix}) {
5226+
return prefix + use(promiseC) + use(promiseD);
5227+
}
5228+
5229+
function Parent() {
5230+
return <Child prefix={use(promiseA) + use(promiseB)} />;
5231+
}
5232+
5233+
function App() {
5234+
return (
5235+
<Suspense fallback="Loading...">
5236+
<Parent />
5237+
</Suspense>
5238+
);
5239+
}
5240+
5241+
await act(async () => {
5242+
const {pipe} = renderToPipeableStream(<App />);
5243+
pipe(writable);
5244+
});
5245+
5246+
// TODO: The `act` implementation in this file doesn't unwrap microtasks
5247+
// automatically. We can't use the same `act` we use for Fiber tests
5248+
// because that relies on the mock Scheduler. Doesn't affect any public
5249+
// API but we might want to fix this for our own internal tests.
5250+
//
5251+
// For now, wait for each promise in sequence.
5252+
await act(async () => {
5253+
await promiseA;
5254+
});
5255+
await act(async () => {
5256+
await promiseB;
5257+
});
5258+
await act(async () => {
5259+
await promiseC;
5260+
});
5261+
await act(async () => {
5262+
await promiseD;
5263+
});
5264+
5265+
expect(getVisibleChildren(container)).toEqual('ABCD');
5266+
5267+
ReactDOMClient.hydrateRoot(container, <App />);
5268+
expect(Scheduler).toFlushAndYield([]);
5269+
expect(getVisibleChildren(container)).toEqual('ABCD');
5270+
});
5271+
5272+
// @gate enableUseHook
5273+
it('using a rejected promise will throw', async () => {
5274+
const promiseA = Promise.resolve('A');
5275+
const promiseB = Promise.reject(new Error('Oops!'));
5276+
const promiseC = Promise.resolve('C');
5277+
5278+
// Jest/Node will raise an unhandled rejected error unless we await this. It
5279+
// works fine in the browser, though.
5280+
await expect(promiseB).rejects.toThrow('Oops!');
5281+
5282+
function Async() {
5283+
return use(promiseA) + use(promiseB) + use(promiseC);
5284+
}
5285+
5286+
class ErrorBoundary extends React.Component {
5287+
state = {error: null};
5288+
static getDerivedStateFromError(error) {
5289+
return {error};
5290+
}
5291+
render() {
5292+
if (this.state.error) {
5293+
return this.state.error.message;
5294+
}
5295+
return this.props.children;
5296+
}
5297+
}
5298+
5299+
function App() {
5300+
return (
5301+
<Suspense fallback="Loading...">
5302+
<ErrorBoundary>
5303+
<Async />
5304+
</ErrorBoundary>
5305+
</Suspense>
5306+
);
5307+
}
5308+
5309+
const reportedServerErrors = [];
5310+
await act(async () => {
5311+
const {pipe} = renderToPipeableStream(<App />, {
5312+
onError(error) {
5313+
reportedServerErrors.push(error);
5314+
},
5315+
});
5316+
pipe(writable);
5317+
});
5318+
5319+
// TODO: The `act` implementation in this file doesn't unwrap microtasks
5320+
// automatically. We can't use the same `act` we use for Fiber tests
5321+
// because that relies on the mock Scheduler. Doesn't affect any public
5322+
// API but we might want to fix this for our own internal tests.
5323+
//
5324+
// For now, wait for each promise in sequence.
5325+
await act(async () => {
5326+
await promiseA;
5327+
});
5328+
await act(async () => {
5329+
await expect(promiseB).rejects.toThrow('Oops!');
5330+
});
5331+
await act(async () => {
5332+
await promiseC;
5333+
});
5334+
5335+
expect(getVisibleChildren(container)).toEqual('Loading...');
5336+
expect(reportedServerErrors.length).toBe(1);
5337+
expect(reportedServerErrors[0].message).toBe('Oops!');
5338+
5339+
const reportedClientErrors = [];
5340+
ReactDOMClient.hydrateRoot(container, <App />, {
5341+
onRecoverableError(error) {
5342+
reportedClientErrors.push(error);
5343+
},
5344+
});
5345+
expect(Scheduler).toFlushAndYield([]);
5346+
expect(getVisibleChildren(container)).toEqual('Oops!');
5347+
expect(reportedClientErrors.length).toBe(1);
5348+
if (__DEV__) {
5349+
expect(reportedClientErrors[0].message).toBe('Oops!');
5350+
} else {
5351+
expect(reportedClientErrors[0].message).toBe(
5352+
'The server could not finish this Suspense boundary, likely due to ' +
5353+
'an error during server rendering. Switched to client rendering.',
5354+
);
5355+
}
5356+
});
5357+
5358+
// @gate enableUseHook
5359+
it("use a promise that's already been instrumented and resolved", async () => {
5360+
const thenable = {
5361+
status: 'fulfilled',
5362+
value: 'Hi',
5363+
then() {},
5364+
};
5365+
5366+
// This will never suspend because the thenable already resolved
5367+
function App() {
5368+
return use(thenable);
5369+
}
5370+
5371+
await act(async () => {
5372+
const {pipe} = renderToPipeableStream(<App />);
5373+
pipe(writable);
5374+
});
5375+
expect(getVisibleChildren(container)).toEqual('Hi');
5376+
5377+
ReactDOMClient.hydrateRoot(container, <App />);
5378+
expect(Scheduler).toFlushAndYield([]);
5379+
expect(getVisibleChildren(container)).toEqual('Hi');
5380+
});
51695381
});
51705382

51715383
describe('useEvent', () => {

packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js

-1
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,6 @@ describe('ReactFlightDOMBrowser', () => {
462462
});
463463

464464
// @gate enableUseHook
465-
// @gate FIXME // Depends on `use` (which was temporarily reverted in Fizz)
466465
it('should allow an alternative module mapping to be used for SSR', async () => {
467466
function ClientComponent() {
468467
return <span>Client Component</span>;

0 commit comments

Comments
 (0)