Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSX Spread tag proposal #63

Closed
quassnoi opened this issue Sep 26, 2016 · 5 comments
Closed

JSX Spread tag proposal #63

quassnoi opened this issue Sep 26, 2016 · 5 comments

Comments

@quassnoi
Copy link

quassnoi commented Sep 26, 2016

This is a feature request.

It is a common request from developers to allow returning multiple elements from inline expressions in JSX.

http://stackoverflow.com/questions/23840997/how-to-return-multiple-lines-jsx-in-another-return-statement-in-react

A use case for this would be this pseudocode:

<Parent>{ flag ? <Child/> : <Sibling1/><Sibling2/><Sibling3/>}</Parent>

which would have React generate either <Parent><Child/></Parent> or <Parent><Sibling1/><Sibling2/><Sibling3/></Parent>, depending on the value of the flag.

As it stands now, this would not work, as two or more consecutive tags would not transpile to a single expression.

There are several approaches to that problem:

  1. Wrap the elements into some kind of a parent tag:

    <Parent>{ flag ? <Child/> : <DummyTag><Sibling1/><Sibling2/><Sibling3/></DummyTag>}</Parent>
    

    This is not always acceptable.

  2. Use arrays:

    <Parent>{ flag ? [<Child/>] : [<Sibling1/><Sibling2/><Sibling3/>]}</Parent>
    

    This would make React complain on the elements in the second array having no unique key property, and adding that property would take some extra effort.

  3. Use keyed fragments (Add fragment API to allow returning multiple components from render react#2127)

    <Parent>{ flag ? <Child/> : React.createFragment({ sibling1: <Sibling1/>, sibling2: <Sibling2/>, sibling3: <Sibling3/>})}</Parent>
    

    This requires assigning arbitrary keys to the fragments, the syntax is quite cumbersome and it's not JSX.

  4. Use createElement directly, making benefit of ES6 spread syntax:

    React.createElement('parent', {}, ...(flag ? [<Child/>] : [<Sibling1/><Sibling2/><Sibling3/>]))
    

    This is the most straightforward way, as it would be equivalent to either React.createElement('parent', {}, <Child/>) or React.createElement('parent', {}, <Sibling1/>, <Sibling2/>, <Sibling3/>), but it's not JSX either.

My proposal is to make use of the latter solution in JSX, extending JSX with a Spread tag: <...>

This tag could only be used as a non-top element in a JSX tree and could only contain a single JS expression in curly braces, which must evaluate to an array.

This Spread tag <...>{ expression }</...> would transpile to ES6 array spread syntax: ...(expression), so that <Parent><a/><...>{ expression }</...><b/></Parent> becomes React.createElement('parent', {}, React.createElement('a', {}), ...(expression), React.createElement('b', '{}'))

This way, the original problem could be solved by writing:

<Parent/><...>{ flag ? [<Child/>] : [<Sibling1>, <Sibling2>, <Sibling3>]}</...><Parent/>

which would transpile as follows:

React.createElement('parent', {},
    ...(flag ?
        [React.createElement('child', {})] : 
        [React.createElement('sibling1', {}),
         React.createElement('sibling2', {}),
         React.createElement('sibling3', {})]
    )
);

This is almost as simple as the naive solution at the very top, and it produces the exact effect most developers are after: variable number of arguments to createElement with no need to create keys or wrap elements into dummy tags.

Also, it's a pure JSX feature which does not introduce any new React code.

@syranide
Copy link
Contributor

I would say that spreading is not what you want, you want the same behavior as you would get with arrays (if you ignore the key warning) with it being its own hierarchy and implicit indices computed accordingly. A previously discussed syntax is <>, which is also consistent with E4X IIRC (for whatever it's worth).

Also, I imagine if we want to support spreading there's already a solution that wouldn't necessarily introduce new syntax <Parent>{...foobar}</Parent>. Again, not sure why you would want it though as opposed to just fragments.

@quassnoi
Copy link
Author

quassnoi commented Sep 26, 2016

Don't I have to key the fragments?

Actually come to think of it, <Parent>{...foobar}</Parent> would be nice too. That's what I was naively trying to do in the first place, only to stumble upon an error saying JSXSpreadAttribute being not in the right place. I just failed to find any proposals about this syntax or <>.

Is there any conceptual difference in React between

  • createElement(tag, styles, a, b, c, d)
  • createElement(tag, styles, a, [b, c], d)
  • createElement(tag, styles, a, createFragment({b, c}), d)

?

@syranide
Copy link
Contributor

syranide commented Sep 26, 2016

Don't I have to key the fragments?

@quassnoi Using React.createFragment naively requires it yes, it's essentially an associative list (i.e. Map), so everything has to be keyed by necessity. But you could easily feed an array to React.createFragment (use index as key) or have JSX pre-compute the indices and assign keys accordingly.

I just failed to find any proposals about this syntax or <>.

Yeah, I'm not 100% sure TBH. It may just have been "informal/internal" discussions in other places.

Is there any conceptual difference in React between createElement(tag, styles, a, b, c, d) and createElement(tag, styles, a, [b, c], d)?

Yes, a very important one. Removing c in the non-array case would cause React to destroy the instance for d and try to reuse the instance for c but now with the props from d. In the second everything would happen just as you would normally expect.

@quassnoi
Copy link
Author

Are you saying I can just pass an array to createFragment and it will accept it, keying by the index, with no extra effort from the developer's side?

@syranide
Copy link
Contributor

@quassnoi Honestly not sure, but you could easily have a wrapper that does it, it's trivial logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants