diff --git a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js
index ce8403941725..2ad9a6852bb9 100644
--- a/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js
+++ b/packages/babel-plugin-react-jsx/__tests__/TransformJSXToReactJSX-test.js
@@ -481,9 +481,24 @@ describe('transform react to jsx', () => {
).toMatchSnapshot();
});
- it('should not contain duplicate children key in props object', () => {
+ it('duplicate children prop should transform into sequence expression with actual children', () => {
expect(
transform(`2`)
).toMatchSnapshot();
});
+ it('duplicate children prop should transform into sequence expression with next prop', () => {
+ expect(
+ transform(`2`)
+ ).toMatchSnapshot();
+ });
+ it('duplicate children props should transform into sequence expression with next prop', () => {
+ expect(
+ transform(`2`)
+ ).toMatchSnapshot();
+ });
+ it('duplicate children prop should transform into sequence expression with spread', () => {
+ expect(
+ transform(`2`)
+ ).toMatchSnapshot();
+ });
});
diff --git a/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap
index a335ef3806b0..22b83d9ec151 100644
--- a/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap
+++ b/packages/babel-plugin-react-jsx/__tests__/__snapshots__/TransformJSXToReactJSX-test.js.snap
@@ -46,6 +46,32 @@ var x = React.jsxs("div", {
});
`;
+exports[`transform react to jsx duplicate children prop should transform into sequence expression with actual children 1`] = `
+React.jsx(Component, {
+ children: (1, "2")
+});
+`;
+
+exports[`transform react to jsx duplicate children prop should transform into sequence expression with next prop 1`] = `
+React.jsx(Component, {
+ foo: (1, 3),
+ children: "2"
+});
+`;
+
+exports[`transform react to jsx duplicate children prop should transform into sequence expression with spread 1`] = `
+React.jsx(Component, Object.assign({}, (1, x), {
+ children: "2"
+}));
+`;
+
+exports[`transform react to jsx duplicate children props should transform into sequence expression with next prop 1`] = `
+React.jsx(Component, {
+ foo: (1, 4, 3),
+ children: "2"
+});
+`;
+
exports[`transform react to jsx fragment with no children 1`] = `var x = React.jsx(React.Fragment, {});`;
exports[`transform react to jsx fragments 1`] = `
@@ -250,14 +276,6 @@ var e = React.jsx(F, {
});
`;
-exports[`transform react to jsx should not contain duplicate children key in props object 1`] = `
-React.jsx(Component, Object.assign({
- children: 1
-}, {
- children: "2"
-}));
-`;
-
exports[`transform react to jsx should not strip nbsp even couple with other whitespace 1`] = `
React.jsx("div", {
children: "\\xA0 "
diff --git a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
index 6d49d825eba2..308fc7116f8c 100644
--- a/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
+++ b/packages/babel-plugin-react-jsx/src/TransformJSXToReactBabelPlugin.js
@@ -119,8 +119,8 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
}
}
- function convertAttribute(node) {
- const value = convertAttributeValue(node.value || t.booleanLiteral(true));
+ function convertAttribute(node, duplicateChildren) {
+ let value = convertAttributeValue(node.value || t.booleanLiteral(true));
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
value.value = value.value.replace(/\n\s+/g, ' ');
@@ -130,6 +130,9 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
delete value.extra.raw;
}
}
+ if (duplicateChildren && duplicateChildren.length > 0) {
+ value = t.sequenceExpression([...duplicateChildren, value]);
+ }
if (t.isJSXNamespacedName(node.name)) {
node.name = t.stringLiteral(
@@ -281,6 +284,17 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
function buildJSXOpeningElementAttributes(attribs, file, children) {
let _props = [];
const objs = [];
+
+ // In order to avoid having duplicate "children" keys, we avoid
+ // pushing the "children" prop if we have actual children. However,
+ // the children prop may have side effects, so to be certain
+ // these side effects are evaluated, we add them to the following prop
+ // as a sequence expression to preserve order. So:
+ //
{child}
becomes
+ // React.jsx('div', {foo: (x++, y), children: child});
+ // duplicateChildren contains the extra children prop values
+ let duplicateChildren = [];
+
const hasChildren = children && children.length > 0;
const useBuiltIns = file.opts.useBuiltIns || false;
@@ -293,19 +307,23 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
while (attribs.length) {
const prop = attribs.shift();
- if (t.isJSXSpreadAttribute(prop)) {
+ if (hasChildren && isChildrenProp(prop)) {
+ duplicateChildren.push(convertAttributeValue(prop.value));
+ } else if (t.isJSXSpreadAttribute(prop)) {
_props = pushProps(_props, objs);
- objs.push(prop.argument);
- } else if (hasChildren && isChildrenProp(prop)) {
- // In order to avoid having duplicate "children" keys, we avoid
- // pushing the "children" prop if we have actual children. Instead
- // we put the children into a separate object and then rely on
- // the Object.assign logic below to ensure the correct object is
- // formed.
- _props = pushProps(_props, objs);
- objs.push(t.objectExpression([convertAttribute(prop)]));
+ if (duplicateChildren.length > 0) {
+ objs.push(
+ t.sequenceExpression([...duplicateChildren, prop.argument]),
+ );
+ duplicateChildren = [];
+ } else {
+ objs.push(prop.argument);
+ }
} else {
- _props.push(convertAttribute(prop));
+ _props.push(convertAttribute(prop, duplicateChildren));
+ if (duplicateChildren.length > 0) {
+ duplicateChildren = [];
+ }
}
}
@@ -313,12 +331,24 @@ You can turn on the 'throwIfNamespace' flag to bypass this warning.`,
// through the argument object
if (hasChildren) {
if (children.length === 1) {
- _props.push(t.objectProperty(t.identifier('children'), children[0]));
+ _props.push(
+ t.objectProperty(
+ t.identifier('children'),
+ duplicateChildren.length > 0
+ ? t.sequenceExpression([...duplicateChildren, children[0]])
+ : children[0],
+ ),
+ );
} else {
_props.push(
t.objectProperty(
t.identifier('children'),
- t.arrayExpression(children),
+ duplicateChildren.length > 0
+ ? t.sequenceExpression([
+ ...duplicateChildren,
+ t.arrayExpression(children),
+ ])
+ : t.arrayExpression(children),
),
);
}