Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
628f6f50b5 | ||
|
|
9b78e66d94 | ||
|
|
64f636b437 |
@@ -78,6 +78,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||||||
ReactFeatureFlags.enableSuspenseCallback = true;
|
ReactFeatureFlags.enableSuspenseCallback = true;
|
||||||
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
|
ReactFeatureFlags.enableDeprecatedFlareAPI = true;
|
||||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||||
|
ReactFeatureFlags.enableSuspenseServerRenderer = true;
|
||||||
|
|
||||||
React = require('react');
|
React = require('react');
|
||||||
ReactDOM = require('react-dom');
|
ReactDOM = require('react-dom');
|
||||||
@@ -2563,4 +2564,49 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||||||
// Now we're hydrated.
|
// Now we're hydrated.
|
||||||
expect(ref.current).not.toBe(null);
|
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', () => {
|
if (!__EXPERIMENTAL__) {
|
||||||
const div = document.createElement('div');
|
it('should throw rendering portals on the server', () => {
|
||||||
expect(() => {
|
const div = document.createElement('div');
|
||||||
ReactDOMServer.renderToString(
|
expect(() => {
|
||||||
<div>{ReactDOM.createPortal(<div />, div)}</div>,
|
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', () => {
|
it('should be able to render and hydrate Mode components', () => {
|
||||||
class ComponentWithWarning extends React.Component {
|
class ComponentWithWarning extends React.Component {
|
||||||
|
|||||||
@@ -900,16 +900,29 @@ class ReactDOMServerRenderer {
|
|||||||
try {
|
try {
|
||||||
outBuffer += this.render(child, frame.context, frame.domNamespace);
|
outBuffer += this.render(child, frame.context, frame.domNamespace);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err != null && typeof err.then === 'function') {
|
if (
|
||||||
|
err != null &&
|
||||||
|
(typeof err.then === 'function' || err === REACT_PORTAL_TYPE)
|
||||||
|
) {
|
||||||
if (enableSuspenseServerRenderer) {
|
if (enableSuspenseServerRenderer) {
|
||||||
invariant(
|
if (err === REACT_PORTAL_TYPE) {
|
||||||
this.suspenseDepth > 0,
|
invariant(
|
||||||
// TODO: include component name. This is a bit tricky with current factoring.
|
this.suspenseDepth > 0,
|
||||||
'A React component suspended while rendering, but no fallback UI was specified.\n' +
|
'Portals must have a fallback UI when being rendered on the server.\n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'Add a <Suspense fallback=...> component higher in the tree to ' +
|
'Add a <Suspense fallback=...> component higher in the tree to ' +
|
||||||
'provide a loading indicator or placeholder to display.',
|
'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;
|
suspended = true;
|
||||||
} else {
|
} else {
|
||||||
invariant(false, 'ReactDOMServer does not yet support Suspense.');
|
invariant(false, 'ReactDOMServer does not yet support Suspense.');
|
||||||
@@ -961,11 +974,17 @@ class ReactDOMServerRenderer {
|
|||||||
if (nextChild != null && nextChild.$$typeof != null) {
|
if (nextChild != null && nextChild.$$typeof != null) {
|
||||||
// Catch unexpected special types early.
|
// Catch unexpected special types early.
|
||||||
const $$typeof = nextChild.$$typeof;
|
const $$typeof = nextChild.$$typeof;
|
||||||
invariant(
|
if ($$typeof === REACT_PORTAL_TYPE) {
|
||||||
$$typeof !== REACT_PORTAL_TYPE,
|
if (enableSuspenseServerRenderer) {
|
||||||
'Portals are not currently supported by the server renderer. ' +
|
throw $$typeof;
|
||||||
'Render them conditionally so that they only appear on the client render.',
|
} 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.
|
// Catch-all to prevent an infinite loop if React.Children.toArray() supports some new type.
|
||||||
invariant(
|
invariant(
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -344,5 +344,6 @@
|
|||||||
"343": "ReactDOMServer does not yet support scope components.",
|
"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.",
|
"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.",
|
"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