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), ), ); }