Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
628f6f50b5 | ||
|
|
9b78e66d94 | ||
|
|
64f636b437 |
@@ -78,6 +78,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
ReactFeatureFlags.enableSuspenseCallback = true;
|
||||
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.enableSuspenseServerRenderer = true;
|
||||
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
@@ -2563,4 +2564,49 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
// Now we're hydrated.
|
||||
expect(ref.current).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should return fallback during server rendering when Portal has a suspense boundary ', () => {
|
||||
const portalContainer = document.createElement('div');
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<React.Suspense fallback={'Loading...'}>
|
||||
{ReactDOM.createPortal(<div>{'Portal'}</div>, portalContainer)}
|
||||
</React.Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const markup = ReactDOMServer.renderToString(<App />);
|
||||
expect(markup).toContain('Loading...');
|
||||
expect(portalContainer.textContent).toBe('');
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = markup;
|
||||
|
||||
// Hydrating this cames the fallback to be removed and the Portal to be added.
|
||||
const root = ReactDOM.createRoot(container, {hydrate: true});
|
||||
act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(portalContainer.textContent).toBe('Portal');
|
||||
expect(container.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should error during server rendering when Portal does not have a Suspense boundary', () => {
|
||||
const portalContainer = document.createElement('div');
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
{ReactDOM.createPortal(<div>{'Portal'}</div>, portalContainer)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<App />)).toThrow(
|
||||
'Portals must have a fallback UI when being rendered on the server.\n\n' +
|
||||
'Add a <Suspense fallback=...> component higher in the tree to provide a ' +
|
||||
'loading indicator or placeholder to display.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -324,17 +324,19 @@ describe('ReactDOMServerHydration', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw rendering portals on the server', () => {
|
||||
const div = document.createElement('div');
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(
|
||||
<div>{ReactDOM.createPortal(<div />, div)}</div>,
|
||||
if (!__EXPERIMENTAL__) {
|
||||
it('should throw rendering portals on the server', () => {
|
||||
const div = document.createElement('div');
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(
|
||||
<div>{ReactDOM.createPortal(<div />, div)}</div>,
|
||||
);
|
||||
}).toThrow(
|
||||
'Portals are not currently supported by the server renderer. ' +
|
||||
'Render them conditionally so that they only appear on the client render.',
|
||||
);
|
||||
}).toThrow(
|
||||
'Portals are not currently supported by the server renderer. ' +
|
||||
'Render them conditionally so that they only appear on the client render.',
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('should be able to render and hydrate Mode components', () => {
|
||||
class ComponentWithWarning extends React.Component {
|
||||
|
||||
@@ -900,16 +900,29 @@ class ReactDOMServerRenderer {
|
||||
try {
|
||||
outBuffer += this.render(child, frame.context, frame.domNamespace);
|
||||
} catch (err) {
|
||||
if (err != null && typeof err.then === 'function') {
|
||||
if (
|
||||
err != null &&
|
||||
(typeof err.then === 'function' || err === REACT_PORTAL_TYPE)
|
||||
) {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
invariant(
|
||||
this.suspenseDepth > 0,
|
||||
// TODO: include component name. This is a bit tricky with current factoring.
|
||||
'A React component suspended while rendering, but no fallback UI was specified.\n' +
|
||||
'\n' +
|
||||
'Add a <Suspense fallback=...> component higher in the tree to ' +
|
||||
'provide a loading indicator or placeholder to display.',
|
||||
);
|
||||
if (err === REACT_PORTAL_TYPE) {
|
||||
invariant(
|
||||
this.suspenseDepth > 0,
|
||||
'Portals must have a fallback UI when being rendered on the server.\n' +
|
||||
'\n' +
|
||||
'Add a <Suspense fallback=...> component higher in the tree to ' +
|
||||
'provide a loading indicator or placeholder to display.',
|
||||
);
|
||||
} else {
|
||||
invariant(
|
||||
this.suspenseDepth > 0,
|
||||
// TODO: include component name. This is a bit tricky with current factoring.
|
||||
'A React component suspended while rendering, but no fallback UI was specified.\n' +
|
||||
'\n' +
|
||||
'Add a <Suspense fallback=...> component higher in the tree to ' +
|
||||
'provide a loading indicator or placeholder to display.',
|
||||
);
|
||||
}
|
||||
suspended = true;
|
||||
} else {
|
||||
invariant(false, 'ReactDOMServer does not yet support Suspense.');
|
||||
@@ -961,11 +974,17 @@ class ReactDOMServerRenderer {
|
||||
if (nextChild != null && nextChild.$$typeof != null) {
|
||||
// Catch unexpected special types early.
|
||||
const $$typeof = nextChild.$$typeof;
|
||||
invariant(
|
||||
$$typeof !== REACT_PORTAL_TYPE,
|
||||
'Portals are not currently supported by the server renderer. ' +
|
||||
'Render them conditionally so that they only appear on the client render.',
|
||||
);
|
||||
if ($$typeof === REACT_PORTAL_TYPE) {
|
||||
if (enableSuspenseServerRenderer) {
|
||||
throw $$typeof;
|
||||
} else {
|
||||
invariant(
|
||||
false,
|
||||
'Portals are not currently supported by the server renderer. ' +
|
||||
'Render them conditionally so that they only appear on the client render.',
|
||||
);
|
||||
}
|
||||
}
|
||||
// Catch-all to prevent an infinite loop if React.Children.toArray() supports some new type.
|
||||
invariant(
|
||||
false,
|
||||
|
||||
@@ -344,5 +344,6 @@
|
||||
"343": "ReactDOMServer does not yet support scope components.",
|
||||
"344": "Expected prepareToHydrateHostSuspenseInstance() to never be called. This error is likely caused by a bug in React. Please file an issue.",
|
||||
"345": "Root did not complete. This is a bug in React.",
|
||||
"346": "An event responder context was used outside of an event cycle."
|
||||
"346": "An event responder context was used outside of an event cycle.",
|
||||
"347": "Portals must have a fallback UI when being rendered on the server.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user