diff --git a/src/renderers/__tests__/ReactComponent-test.js b/src/renderers/__tests__/ReactComponent-test.js
index 30503913da052..dbea2bd3dcd5f 100644
--- a/src/renderers/__tests__/ReactComponent-test.js
+++ b/src/renderers/__tests__/ReactComponent-test.js
@@ -13,6 +13,7 @@
var React;
var ReactDOM;
+var ReactDOMServer;
var ReactDOMFeatureFlags;
var ReactTestUtils;
@@ -24,6 +25,7 @@ describe('ReactComponent', () => {
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
+ ReactDOMServer = require('react-dom/server');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
ReactTestUtils = require('react-dom/test-utils');
});
@@ -439,4 +441,56 @@ describe('ReactComponent', () => {
' in Foo (at **)',
);
});
+
+ it('throws if a plain object is used as a child when using SSR', async () => {
+ var children = {
+ x: ,
+ y: ,
+ z: ,
+ };
+ var element =
{[children]}
;
+ var ex;
+ try {
+ ReactDOMServer.renderToString(element);
+ } catch (e) {
+ ex = e;
+ }
+ expect(ex).toBeDefined();
+ expect(normalizeCodeLocInfo(ex.message)).toBe(
+ 'Objects are not valid as a React child (found: object with keys ' +
+ '{x, y, z}). If you meant to render a collection of children, use ' +
+ 'an array instead.' +
+ // Fiber gives a slightly better stack with the nearest host components
+ (ReactDOMFeatureFlags.useFiber ? '\n in div (at **)' : ''),
+ );
+ });
+
+ it('throws if a plain object even if it is in an owner when using SSR', async () => {
+ class Foo extends React.Component {
+ render() {
+ var children = {
+ a: ,
+ b: ,
+ c: ,
+ };
+ return {[children]}
;
+ }
+ }
+ var container = document.createElement('div');
+ var ex;
+ try {
+ ReactDOMServer.renderToString(, container);
+ } catch (e) {
+ ex = e;
+ }
+ expect(ex).toBeDefined();
+ expect(normalizeCodeLocInfo(ex.message)).toBe(
+ 'Objects are not valid as a React child (found: object with keys ' +
+ '{a, b, c}). If you meant to render a collection of children, use ' +
+ 'an array instead.\n' +
+ // Fiber gives a slightly better stack with the nearest host components
+ (ReactDOMFeatureFlags.useFiber ? ' in div (at **)\n' : '') +
+ ' in Foo (at **)',
+ );
+ });
});
diff --git a/src/renderers/dom/ReactDOMNodeStreamRenderer.js b/src/renderers/dom/ReactDOMNodeStreamRenderer.js
index 890538bb6fbd4..c5379be7290bd 100644
--- a/src/renderers/dom/ReactDOMNodeStreamRenderer.js
+++ b/src/renderers/dom/ReactDOMNodeStreamRenderer.js
@@ -14,6 +14,7 @@
var invariant = require('fbjs/lib/invariant');
var React = require('react');
var ReactPartialRenderer = require('ReactPartialRenderer');
+var ReactFeatureFlags = require('ReactFeatureFlags');
var Readable = require('stream').Readable;
@@ -40,10 +41,13 @@ class ReactMarkupReadableStream extends Readable {
* See https://facebook.github.io/react/docs/react-dom-stream.html#rendertostream
*/
function renderToStream(element) {
- invariant(
- React.isValidElement(element),
- 'renderToStream(): You must pass a valid ReactElement.',
- );
+ const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures;
+ if (disableNewFiberFeatures) {
+ invariant(
+ React.isValidElement(element),
+ 'renderToStream(): You must pass a valid ReactElement.',
+ );
+ }
return new ReactMarkupReadableStream(element, false);
}
@@ -53,10 +57,13 @@ function renderToStream(element) {
* See https://facebook.github.io/react/docs/react-dom-stream.html#rendertostaticstream
*/
function renderToStaticStream(element) {
- invariant(
- React.isValidElement(element),
- 'renderToStaticStream(): You must pass a valid ReactElement.',
- );
+ const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures;
+ if (disableNewFiberFeatures) {
+ invariant(
+ React.isValidElement(element),
+ 'renderToStaticStream(): You must pass a valid ReactElement.',
+ );
+ }
return new ReactMarkupReadableStream(element, true);
}
diff --git a/src/renderers/dom/ReactDOMStringRenderer.js b/src/renderers/dom/ReactDOMStringRenderer.js
index 7995ac3581bcd..7461c3297c837 100644
--- a/src/renderers/dom/ReactDOMStringRenderer.js
+++ b/src/renderers/dom/ReactDOMStringRenderer.js
@@ -14,6 +14,7 @@
var invariant = require('fbjs/lib/invariant');
var React = require('react');
var ReactPartialRenderer = require('ReactPartialRenderer');
+var ReactFeatureFlags = require('ReactFeatureFlags');
/**
* Render a ReactElement to its initial HTML. This should only be used on the
@@ -21,10 +22,13 @@ var ReactPartialRenderer = require('ReactPartialRenderer');
* See https://facebook.github.io/react/docs/react-dom-server.html#rendertostring
*/
function renderToString(element) {
- invariant(
- React.isValidElement(element),
- 'renderToString(): You must pass a valid ReactElement.',
- );
+ const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures;
+ if (disableNewFiberFeatures) {
+ invariant(
+ React.isValidElement(element),
+ 'renderToString(): You must pass a valid ReactElement.',
+ );
+ }
var renderer = new ReactPartialRenderer(element, false);
var markup = renderer.read(Infinity);
return markup;
@@ -36,10 +40,13 @@ function renderToString(element) {
* See https://facebook.github.io/react/docs/react-dom-server.html#rendertostaticmarkup
*/
function renderToStaticMarkup(element) {
- invariant(
- React.isValidElement(element),
- 'renderToStaticMarkup(): You must pass a valid ReactElement.',
- );
+ const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures;
+ if (disableNewFiberFeatures) {
+ invariant(
+ React.isValidElement(element),
+ 'renderToStaticMarkup(): You must pass a valid ReactElement.',
+ );
+ }
var renderer = new ReactPartialRenderer(element, true);
var markup = renderer.read(Infinity);
return markup;
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
index 6f74ed2b48f30..d7f8fe3ee2032 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMServerIntegration-test.js
@@ -332,6 +332,19 @@ describe('ReactDOMServerIntegration', () => {
});
describe('basic rendering', function() {
+ beforeEach(() => {
+ onAfterResetModules = () => {
+ const ReactFeatureFlags = require('ReactFeatureFlags');
+ ReactFeatureFlags.disableNewFiberFeatures = false;
+ };
+
+ resetModules();
+ });
+
+ afterEach(() => {
+ onAfterResetModules = null;
+ });
+
itRenders('a blank div', async render => {
const e = await render();
expect(e.tagName).toBe('DIV');
@@ -347,6 +360,70 @@ describe('ReactDOMServerIntegration', () => {
expect(e.childNodes.length).toBe(1);
expect(e.firstChild.tagName).toBe('BR');
});
+
+ if (ReactDOMFeatureFlags.useFiber) {
+ itRenders('a array type children as a child', async render => {
+ let Header = props => {
+ return header
;
+ };
+ let Footer = props => {
+ return [footer
, about
];
+ };
+ let e = await render([
+ text1
,
+ text2,
+ ,
+ ,
+ ]);
+ let parent = e.parentNode;
+ expect(parent.childNodes[0].tagName).toBe('DIV');
+ expect(parent.childNodes[1].tagName).toBe('SPAN');
+ expect(parent.childNodes[2].tagName).toBe('P');
+ expect(parent.childNodes[3].tagName).toBe('H2');
+ expect(parent.childNodes[4].tagName).toBe('H3');
+ });
+
+ itRenders('a single array element children as a child', async render => {
+ let e = await render([text1
]);
+ let parent = e.parentNode;
+ expect(parent.childNodes[0].tagName).toBe('DIV');
+ });
+
+ itRenders('a nested array children as a child', async render => {
+ let e = await render([
+ [text1
],
+ text2,
+ [[[null, ], false]],
+ ]);
+ let parent = e.parentNode;
+ expect(parent.childNodes[0].tagName).toBe('DIV');
+ expect(parent.childNodes[1].tagName).toBe('SPAN');
+ expect(parent.childNodes[2].tagName).toBe('P');
+ });
+
+ itRenders('an iterable as a child', async render => {
+ const threeDivIterable = {
+ '@@iterator': function() {
+ var i = 0;
+ return {
+ next: function() {
+ if (i++ < 3) {
+ return {value: , done: false};
+ } else {
+ return {value: undefined, done: true};
+ }
+ },
+ };
+ },
+ };
+ let e = await render(threeDivIterable);
+ let parent = e.parentNode;
+ expect(parent.childNodes.length).toBe(3);
+ expect(parent.childNodes[0].tagName).toBe('DIV');
+ expect(parent.childNodes[1].tagName).toBe('DIV');
+ expect(parent.childNodes[2].tagName).toBe('DIV');
+ });
+ }
});
describe('property to attribute mapping', function() {
diff --git a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
index b768f3bef2bb2..0658255d6748f 100644
--- a/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactServerRendering-test.js
@@ -20,6 +20,7 @@ var ReactMarkupChecksum;
var ReactReconcileTransaction;
var ReactTestUtils;
var PropTypes;
+var ReactFeatureFlags;
var ID_ATTRIBUTE_NAME;
var ROOT_ATTRIBUTE_NAME;
@@ -35,6 +36,9 @@ describe('ReactDOMServer', () => {
ReactReconcileTransaction = require('ReactReconcileTransaction');
PropTypes = require('prop-types');
+ ReactFeatureFlags = require('ReactFeatureFlags');
+ ReactFeatureFlags.disableNewFiberFeatures = false;
+
ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment');
ExecutionEnvironment.canUseDOM = false;
ReactDOMServer = require('react-dom/server');
@@ -319,8 +323,12 @@ describe('ReactDOMServer', () => {
it('should throw with silly args', () => {
expect(
- ReactDOMServer.renderToString.bind(ReactDOMServer, 'not a component'),
- ).toThrowError('renderToString(): You must pass a valid ReactElement.');
+ ReactDOMServer.renderToString.bind(ReactDOMServer, {x: 123}),
+ ).toThrowError(
+ ReactDOMFeatureFlags.useFiber
+ ? 'Objects are not valid as a React child (found: object with keys {x})'
+ : 'renderToString(): You must pass a valid ReactElement.',
+ );
});
});
@@ -431,12 +439,11 @@ describe('ReactDOMServer', () => {
it('should throw with silly args', () => {
expect(
- ReactDOMServer.renderToStaticMarkup.bind(
- ReactDOMServer,
- 'not a component',
- ),
+ ReactDOMServer.renderToStaticMarkup.bind(ReactDOMServer, {x: 123}),
).toThrowError(
- 'renderToStaticMarkup(): You must pass a valid ReactElement.',
+ ReactDOMFeatureFlags.useFiber
+ ? 'Objects are not valid as a React child (found: object with keys {x})'
+ : 'renderToStaticMarkup(): You must pass a valid ReactElement.',
);
});
diff --git a/src/renderers/shared/server/ReactPartialRenderer.js b/src/renderers/shared/server/ReactPartialRenderer.js
index 18f21f3119b00..1d49808286ecf 100644
--- a/src/renderers/shared/server/ReactPartialRenderer.js
+++ b/src/renderers/shared/server/ReactPartialRenderer.js
@@ -307,11 +307,6 @@ function createOpenTagMarkup(
}
function resolve(child, context) {
- // TODO: We'll need to support Arrays (and strings) after Fiber is rolled out
- invariant(
- !Array.isArray(child),
- 'The server renderer does not implement support for array children yet.',
- );
while (React.isValidElement(child)) {
if (__DEV__) {
pushElementToDebugStack(child);
@@ -418,8 +413,9 @@ function resolve(child, context) {
class ReactDOMServerRenderer {
constructor(element, makeStaticMarkup) {
+ var children = React.isValidElement(element) ? [element] : toArray(element);
var topFrame = {
- children: [element],
+ children,
childIndex: 0,
context: emptyObject,
footer: '',
@@ -487,7 +483,22 @@ class ReactDOMServerRenderer {
if (child === null || child === false) {
return '';
} else {
- return this.renderDOM(child, context);
+ if (React.isValidElement(child)) {
+ return this.renderDOM(child, context);
+ } else {
+ var children = toArray(child);
+ var frame = {
+ children,
+ childIndex: 0,
+ context: context,
+ footer: '',
+ };
+ if (__DEV__) {
+ frame.debugElementStack = [];
+ }
+ this.stack.push(frame);
+ return '';
+ }
}
}
}