{"action":"created","issue":{"url":"https://api.github.com/repos/facebook/jsx/issues/57","repository_url":"https://api.github.com/repos/facebook/jsx","labels_url":"https://api.github.com/repos/facebook/jsx/issues/57/labels{/name}","comments_url":"https://api.github.com/repos/facebook/jsx/issues/57/comments","events_url":"https://api.github.com/repos/facebook/jsx/issues/57/events","html_url":"https://github.com/facebook/jsx/issues/57","id":160644318,"number":57,"title":"Spec proposal: extending the language to allow spreading children","user":{"login":"calebmer","id":8282507,"avatar_url":"https://avatars.githubusercontent.com/u/8282507?v=3","gravatar_id":"","url":"https://api.github.com/users/calebmer","html_url":"https://github.com/calebmer","followers_url":"https://api.github.com/users/calebmer/followers","following_url":"https://api.github.com/users/calebmer/following{/other_user}","gists_url":"https://api.github.com/users/calebmer/gists{/gist_id}","starred_url":"https://api.github.com/users/calebmer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/calebmer/subscriptions","organizations_url":"https://api.github.com/users/calebmer/orgs","repos_url":"https://api.github.com/users/calebmer/repos","events_url":"https://api.github.com/users/calebmer/events{/privacy}","received_events_url":"https://api.github.com/users/calebmer/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":4,"created_at":"2016-06-16T11:59:05Z","updated_at":"2016-06-17T13:07:09Z","closed_at":null,"body":"I propose we modify the JSX specification to allow for the spreading of child nodes in JSX like so:\r\n\r\n```jsx\r\n<div>{...children}</div>\r\n```\r\n\r\nInstead of the implicit array flattening done by React and other JSX consumers currently.\r\n\r\n## Rationale\r\n\r\nJSX with React allows you to do this:\r\n\r\n```jsx\r\nconst TodoList = ({ todos }) => (\r\n  <div>\r\n    {todos.map(todo => <Todo key={todo.id} todo={todo}/>)}\r\n  </div>\r\n)\r\n```\r\n\r\nHowever, React cleverly hides what is actually happening. If we turn the above JSX into a static object, we get something that looks like this:\r\n\r\n```jsx\r\nconst TodoList = ({ todos }) => ({\r\n  type: 'div',\r\n  children: [\r\n    todos.map(todo => …)\r\n  ],\r\n})\r\n```\r\n\r\nAs you can see we have a nested array:\r\n\r\n```js\r\n[todos.map(todo => …)]\r\n```\r\n\r\nThis requires JSX consumers to flatten arrays before processing them. A step that is not only unnecessary but also difficult to express in a JSX type system that would otherwise look like a tree.\r\n\r\n```ts\r\ntype JSXNode = JSXNodePrimitive | JSXElement\r\n```\r\n\r\nMust instead become something to the effects of:\r\n\r\n```ts\r\ntype JSXNode = JSXNodePrimitive | JSXElement | Iterable<JSXNode>\r\n```\r\n\r\nIt’s strange that we should have this problem, however, when the solution should really be in the language. Already ES6 has introduced the spread operator (`...`), so modifying our above example to:\r\n\r\n```jsx\r\nconst TodoList = ({ todos }) => ({\r\n  type: 'div',\r\n  children: [\r\n    ...todos.map(todo => …)\r\n  ],\r\n})\r\n```\r\n\r\nWill give us the list of list of children the JSX consumer needs without requiring a post processing step. We add the expressiveness to the language and we remove complexity from our JSX consuming libraries (like React).\r\n\r\nIn order to add this extension, we just extend JSXChild to allow for the type `{` `...` AssignmentExpression(opt) `}`.\r\n\r\nThen the above example would become:\r\n\r\n```jsx\r\nconst TodoList = ({ todos }) => (\r\n  <div>\r\n    {...todos.map(todo => <Todo key={todo.id} todo={todo}/>)}\r\n  </div>\r\n)\r\n```\r\n\r\nOr the classic `children` example would be:\r\n\r\n```jsx\r\n<div>{...children}</div>\r\n```\r\n\r\nAnd this would compile to a form we expect at a type level:\r\n\r\n```js\r\n{\r\n  kind: 'div',\r\n  children: [\r\n    ...children,\r\n  ],\r\n}\r\n```\r\n\r\n## Motivation\r\n\r\nReact isn’t the only library/framework which should be able to consume JSX, and we shouldn’t expect every library/framework to adopt React’s JSX “workaround” of flattening child arrays when in fact that is a fault in the language extension.\r\n\r\nTherefore, in order to simplify libraries and avoid extra complexity in JSX consumers we should implement this proposal.\r\n\r\n## Next Steps\r\n\r\nI wanted to submit this proposal here to see if anyone had opinions on it before I started writing code. In the near future I will probably submit a PR to the Babel repository modifying the [`babel-plugin-syntax-jsx`](https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-jsx) plugin adding this change. After that PR I will implement a transform in my general JSX transformation library [`babel-plugin-transform-jsx`](https://www.npmjs.com/package/babel-plugin-transform-jsx). If both experiments are successful we can consider adding the child spread functionality to the JSX spec and other JSX transpilers.\r\n\r\nThanks!"},"comment":{"url":"https://api.github.com/repos/facebook/jsx/issues/comments/226764054","html_url":"https://github.com/facebook/jsx/issues/57#issuecomment-226764054","issue_url":"https://api.github.com/repos/facebook/jsx/issues/57","id":226764054,"user":{"login":"calebmer","id":8282507,"avatar_url":"https://avatars.githubusercontent.com/u/8282507?v=3","gravatar_id":"","url":"https://api.github.com/users/calebmer","html_url":"https://github.com/calebmer","followers_url":"https://api.github.com/users/calebmer/followers","following_url":"https://api.github.com/users/calebmer/following{/other_user}","gists_url":"https://api.github.com/users/calebmer/gists{/gist_id}","starred_url":"https://api.github.com/users/calebmer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/calebmer/subscriptions","organizations_url":"https://api.github.com/users/calebmer/orgs","repos_url":"https://api.github.com/users/calebmer/repos","events_url":"https://api.github.com/users/calebmer/events{/privacy}","received_events_url":"https://api.github.com/users/calebmer/received_events","type":"User","site_admin":false},"created_at":"2016-06-17T13:07:09Z","updated_at":"2016-06-17T13:07:09Z","body":"> If you don't put the burden of flattening the children implicitly on the consumer then it falls on the user of JSX to pre-flatten all children.\r\n\r\nI agree. This is the intended nature. Explicit notion over an implicit conversion.\r\n\r\n> Consider `<div><Header/>{keyedChildren}{indexedChildren}</div>`…\r\n\r\nThis is a good example, but I’d argue here that I’d rather give this case to the user to optimize instead of assuming every JSX runtime will optimize for. A user that understands their runtime would not write JSX that looks like this. In addition, this proposal would not stop React from optimizing for this case.\r\n\r\n> …you would have to explicitly convert the indices of `indexedChildren` to keys and also prefix to avoid conflicts with the `keyedChildren` (which would also need to be prefixed). So you would quickly create a bunch of helpers to deal with this right?\r\n\r\nThis makes two assumptions I:\r\n\r\nOne is that keys are array local. I know I’ve considered instead using `id`s as element keys because an `id` would serve two purposes. First to identify the element in the browser and second to optimize lists. And in this case an `id` must always be global.\r\n\r\nTwo is the assumption that this is a common case and people will choose to write helpers instead of nesting like so: `<div><div>{keyedChildren}</div><div>{indexedChildren}</div></div>`.\r\n\r\nThere are ways to work around this potential performance issue without writing helpers, and if people chose to always use global `id`s instead of `key`s in their framework the point is moot altogether.\r\n\r\n> Could not translate to children: [...children], because as mentioned above you have to consider that children may be maps/sets/iterators/younameit…\r\n\r\nRun this code in an ES6 environment:\r\n\r\n```js\r\n[...new Map([[1, 2], [3, 4]])]\r\n```\r\n\r\nThe result is `[[1, 2], [3, 4]]`. The spread operator supports any [iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols). `Array`, `String`, `Map`, `Set`, the object returned by generators, all are iterables and all are supported by the spread operator. If React wanted to do a special optimization for `Map`s or for another non-iterable object it still can under this proposal. The user just shouldn’t spread. However, the lowest denominator JSX consumer doesn’t have the time or budget to support all of these cases.\r\n\r\nTherefore the spread operator (`...`) does do all that and we can avoid `children: flattenChildren(children)` which I agree is undesirable.\r\n\r\n> It also seems to me the only reason it would be the `...` operator would be to support `<div>{foo}{...children}{bar}</div>`, but that doesn't seem very useful due to implicit keys.\r\n\r\nYep 😊\r\n\r\nAnd again, `key`s are an implementation detail. They are not part of JSX. If we are going to weigh implementation details in deciding whether to adopt the proposal we should weigh the added complexity of requiring users to flatten JSX children. We can solve `key` problems. Either by using global `id`s, educating developers, or by declaring this an anti-pattern for specific frameworks. However, other JSX consumers cannot solve the problem of not needing to flatten children always without this proposal. On balance, I think that’s in favor of adopting this proposal.\r\n\r\n> It seems to me that all this would be doing is transferring the responsibility of flattening children to the user, explicitly requiring the use of `...` everywhere to pre-flatten children.\r\n\r\nYes. You can’t do:\r\n\r\n```js\r\nconst foo = [1, 2, 3]\r\nconst bar = [4, foo, 5]\r\nassert(bar === [4, 1, 2, 3, 5])\r\n```\r\n\r\nSo developers are already expected to spread lists in an array. This proposal is a natural extension of this.\r\n\r\n> You would still need the underlying helpers unless you only intend to support arrays.\r\n\r\n(See my above statement on iterators)\r\n\r\n> The intended benefits seems stripped away by the practical issues encountered?\r\n\r\nI don’t think so, I only think the practical issues are for React. JSX isn’t exclusively for React (or at least I hope).\r\n\r\nIf this proposal gets implemented in `babel-plugin-syntax-jsx`, the React team still has the ability to disallow it. A warning (or error) can be added in Eslint or even in `babel-plugin-transform-react-jsx` itself. This proposal is for the broader community of JSX consumers, not so much for React."}}
{"actor":{"display_login":"calebmer"}}