Prevent infinite loop when SSR-rendering a portal (#11709)
Dan Abramov committed
Nov 29, 2017 at 21:45 UTC
c3f1b6cd91ffa3e05465c3ed2f48225876c1bd3b
2 files changed
+61
-1
packages/react-dom/src/__tests__/ReactServerRendering-test.js
+34
@@ -10,6 +10,7 @@
10
'use strict';
11
12
var React;
13
+var ReactCallReturn;
14
var ReactDOM;
15
var ReactDOMServer;
16
var ReactTestUtils;
@@ -23,6 +24,7 @@ describe('ReactDOMServer', () => {
24
beforeEach(() => {
25
jest.resetModules();
26
React = require('react');
27
+ ReactCallReturn = require('react-call-return');
28
ReactDOM = require('react-dom');
29
ReactTestUtils = require('react-dom/test-utils');
30
PropTypes = require('prop-types');
@@ -772,4 +774,36 @@ describe('ReactDOMServer', () => {
774
);
775
}
776
});
777
+
778
+ it('should throw rendering portals on the server', () => {
779
+ var div = document.createElement('div');
780
+ expect(() => {
781
+ ReactDOMServer.renderToString(
782
+ <div>{ReactDOM.createPortal(<div />, div)}</div>,
783
+ );
784
+ }).toThrow(
785
+ 'Portals are not currently supported by the server renderer. ' +
786
+ 'Render them conditionally so that they only appear on the client render.',
787
+ );
788
+ });
789
+
790
+ it('should throw rendering call/return on the server', () => {
791
+ var div = document.createElement('div');
792
+ expect(() => {
793
+ ReactDOMServer.renderToString(
794
+ <div>{ReactCallReturn.unstable_createReturn(42)}</div>,
795
+ );
796
+ }).toThrow(
797
+ 'The experimental Call and Return types are not currently supported by the server renderer.',
798
+ );
799
+ expect(() => {
800
+ ReactDOMServer.renderToString(
801
+ <div>
802
+ {ReactCallReturn.unstable_createCall(null, function() {}, {})}
803
+ </div>,
804
+ );
805
+ }).toThrow(
806
+ 'The experimental Call and Return types are not currently supported by the server renderer.',
807
+ );
808
+ });
809
});
packages/react-dom/src/server/ReactPartialRenderer.js
+27
-1
@@ -19,7 +19,12 @@ import warning from 'fbjs/lib/warning';
19
import checkPropTypes from 'prop-types/checkPropTypes';
20
import describeComponentFrame from 'shared/describeComponentFrame';
21
import {ReactDebugCurrentFrame} from 'shared/ReactGlobalSharedState';
22
-import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
22
+import {
23
+ REACT_FRAGMENT_TYPE,
24
+ REACT_CALL_TYPE,
25
+ REACT_RETURN_TYPE,
26
+ REACT_PORTAL_TYPE,
27
+} from 'shared/ReactSymbols';
28
29
import {
30
createMarkupForCustomAttribute,
@@ -585,6 +590,27 @@ class ReactDOMServerRenderer {
590
if (nextChild === null || nextChild === false) {
591
return '';
592
} else if (!React.isValidElement(nextChild)) {
593
+ if (nextChild != null && nextChild.$$typeof != null) {
594
+ // Catch unexpected special types early.
595
+ const $$typeof = nextChild.$$typeof;
596
+ invariant(
597
+ $$typeof !== REACT_PORTAL_TYPE,
598
+ 'Portals are not currently supported by the server renderer. ' +
599
+ 'Render them conditionally so that they only appear on the client render.',
600
+ );
601
+ invariant(
602
+ $$typeof !== REACT_CALL_TYPE && $$typeof !== REACT_RETURN_TYPE,
603
+ 'The experimental Call and Return types are not currently ' +
604
+ 'supported by the server renderer.',
605
+ );
606
+ // Catch-all to prevent an infinite loop if React.Children.toArray() supports some new type.
607
+ invariant(
608
+ false,
609
+ 'Unknown element-like object type: %s. This is likely a bug in React. ' +
610
+ 'Please file an issue.',
611
+ ($$typeof: any).toString(),
612
+ );
613
+ }
614
const nextChildren = toArray(nextChild);
615
const frame: Frame = {
616
domNamespace: parentNamespace,