Skip to content

Commit

Permalink
Revert "experimental_use(promise) for SSR (facebook#25214)"
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
acdlite committed Dec 5, 2022
1 parent 0dfb986 commit dedfeff
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 415 deletions.
212 changes: 0 additions & 212 deletions packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ let Suspense;
let SuspenseList;
let useSyncExternalStore;
let useSyncExternalStoreWithSelector;
let use;
let PropTypes;
let textCache;
let window;
Expand All @@ -48,7 +47,6 @@ describe('ReactDOMFizzServer', () => {
ReactDOMFizzServer = require('react-dom/server');
Stream = require('stream');
Suspense = React.Suspense;
use = React.use;
if (gate(flags => flags.enableSuspenseList)) {
SuspenseList = React.SuspenseList;
}
Expand Down Expand Up @@ -5168,216 +5166,6 @@ describe('ReactDOMFizzServer', () => {
console.error = originalConsoleError;
}
});

// @gate enableUseHook
it('basic use(promise)', async () => {
const promiseA = Promise.resolve('A');
const promiseB = Promise.resolve('B');
const promiseC = Promise.resolve('C');

function Async() {
return use(promiseA) + use(promiseB) + use(promiseC);
}

function App() {
return (
<Suspense fallback="Loading...">
<Async />
</Suspense>
);
}

await act(async () => {
const {pipe} = renderToPipeableStream(<App />);
pipe(writable);
});

// TODO: The `act` implementation in this file doesn't unwrap microtasks
// automatically. We can't use the same `act` we use for Fiber tests
// because that relies on the mock Scheduler. Doesn't affect any public
// API but we might want to fix this for our own internal tests.
//
// For now, wait for each promise in sequence.
await act(async () => {
await promiseA;
});
await act(async () => {
await promiseB;
});
await act(async () => {
await promiseC;
});

expect(getVisibleChildren(container)).toEqual('ABC');

ReactDOMClient.hydrateRoot(container, <App />);
expect(Scheduler).toFlushAndYield([]);
expect(getVisibleChildren(container)).toEqual('ABC');
});

// @gate enableUseHook
it('use(promise) in multiple components', async () => {
const promiseA = Promise.resolve('A');
const promiseB = Promise.resolve('B');
const promiseC = Promise.resolve('C');
const promiseD = Promise.resolve('D');

function Child({prefix}) {
return prefix + use(promiseC) + use(promiseD);
}

function Parent() {
return <Child prefix={use(promiseA) + use(promiseB)} />;
}

function App() {
return (
<Suspense fallback="Loading...">
<Parent />
</Suspense>
);
}

await act(async () => {
const {pipe} = renderToPipeableStream(<App />);
pipe(writable);
});

// TODO: The `act` implementation in this file doesn't unwrap microtasks
// automatically. We can't use the same `act` we use for Fiber tests
// because that relies on the mock Scheduler. Doesn't affect any public
// API but we might want to fix this for our own internal tests.
//
// For now, wait for each promise in sequence.
await act(async () => {
await promiseA;
});
await act(async () => {
await promiseB;
});
await act(async () => {
await promiseC;
});
await act(async () => {
await promiseD;
});

expect(getVisibleChildren(container)).toEqual('ABCD');

ReactDOMClient.hydrateRoot(container, <App />);
expect(Scheduler).toFlushAndYield([]);
expect(getVisibleChildren(container)).toEqual('ABCD');
});

// @gate enableUseHook
it('using a rejected promise will throw', async () => {
const promiseA = Promise.resolve('A');
const promiseB = Promise.reject(new Error('Oops!'));
const promiseC = Promise.resolve('C');

// Jest/Node will raise an unhandled rejected error unless we await this. It
// works fine in the browser, though.
await expect(promiseB).rejects.toThrow('Oops!');

function Async() {
return use(promiseA) + use(promiseB) + use(promiseC);
}

class ErrorBoundary extends React.Component {
state = {error: null};
static getDerivedStateFromError(error) {
return {error};
}
render() {
if (this.state.error) {
return this.state.error.message;
}
return this.props.children;
}
}

function App() {
return (
<Suspense fallback="Loading...">
<ErrorBoundary>
<Async />
</ErrorBoundary>
</Suspense>
);
}

const reportedServerErrors = [];
await act(async () => {
const {pipe} = renderToPipeableStream(<App />, {
onError(error) {
reportedServerErrors.push(error);
},
});
pipe(writable);
});

// TODO: The `act` implementation in this file doesn't unwrap microtasks
// automatically. We can't use the same `act` we use for Fiber tests
// because that relies on the mock Scheduler. Doesn't affect any public
// API but we might want to fix this for our own internal tests.
//
// For now, wait for each promise in sequence.
await act(async () => {
await promiseA;
});
await act(async () => {
await expect(promiseB).rejects.toThrow('Oops!');
});
await act(async () => {
await promiseC;
});

expect(getVisibleChildren(container)).toEqual('Loading...');
expect(reportedServerErrors.length).toBe(1);
expect(reportedServerErrors[0].message).toBe('Oops!');

const reportedClientErrors = [];
ReactDOMClient.hydrateRoot(container, <App />, {
onRecoverableError(error) {
reportedClientErrors.push(error);
},
});
expect(Scheduler).toFlushAndYield([]);
expect(getVisibleChildren(container)).toEqual('Oops!');
expect(reportedClientErrors.length).toBe(1);
if (__DEV__) {
expect(reportedClientErrors[0].message).toBe('Oops!');
} else {
expect(reportedClientErrors[0].message).toBe(
'The server could not finish this Suspense boundary, likely due to ' +
'an error during server rendering. Switched to client rendering.',
);
}
});

// @gate enableUseHook
it("use a promise that's already been instrumented and resolved", async () => {
const thenable = {
status: 'fulfilled',
value: 'Hi',
then() {},
};

// This will never suspend because the thenable already resolved
function App() {
return use(thenable);
}

await act(async () => {
const {pipe} = renderToPipeableStream(<App />);
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual('Hi');

ReactDOMClient.hydrateRoot(container, <App />);
expect(Scheduler).toFlushAndYield([]);
expect(getVisibleChildren(container)).toEqual('Hi');
});
});

describe('useEvent', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ describe('ReactFlightDOMBrowser', () => {
});

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

0 comments on commit dedfeff

Please sign in to comment.