jsx icon indicating copy to clipboard operation
jsx copied to clipboard

support object literal property value shorthand

Open bloodyowl opened this issue 10 years ago • 67 comments

side note : following the issue facebook/react/issues/2536

what I'd like to be able to do with jsx is this :

{this.state.list.map((item, key) => <MyComponent {item, key} />)}

I know it's not to be considered a big syntax issue, its just sugar, but it seems logical to me that after supporting the spread operator, as this other ES6 feature is supported by jstransform/jsx

as @sebmarkbage said in the related react issue, the <Foo item /> syntax is supported for boolean attributes, but IMO

var item = "value"
return <Foo {item}/>

isn't that confusing as it is going to get supported by various browsers soon, and developers will have to know it.

so now, I think it's all about discussing this proposal :smile:

bloodyowl avatar Nov 27 '14 21:11 bloodyowl

what I'd like to be able to do with jsx is this :

{this.state.list.map((item, key) => <MyComponent {item, key} />)}

Well, you can do it like following:

{this.state.list.map((item, key) => <MyComponent {...{item, key}} />)}

RReverser avatar Nov 27 '14 21:11 RReverser

this doesn't seem to work so far

bloodyowl avatar Nov 27 '14 21:11 bloodyowl

You probably didn't enable ES6 transformation in your transpiler.

RReverser avatar Nov 27 '14 21:11 RReverser

oh, sorry, I actually did for this test.

but is it ~really~ logical that {...{foo}} is supported when {foo} isn't?

bloodyowl avatar Nov 27 '14 21:11 bloodyowl

Tough question. If React would start supporting {foo}, {foo, bar}, then it would be logical to support {foo: 1}, {foo: 1, bar: function () {}} as well and finally we would just have object literals following tag name. Current spread-like syntax looks more logical.

RReverser avatar Nov 27 '14 22:11 RReverser

Thank you for reporting this issue and appreciate your patience. We've notified the core team for an update on this issue. We're looking for a response within the next 30 days or the issue may be closed.

ghost avatar Aug 04 '15 18:08 ghost

I agree with @bloodyowl in that allowing spread-like syntax and not allowing object literals is not so logical.

But if not object literals then at least <Comp {propA} {propB} {propC}> should be allowed as a shorthand for <Comp propA={propA} propB={propB} propC={propC}>, because it is way easier to read and write.

denvned avatar Nov 09 '15 13:11 denvned

Right now the spec is that JSXSpreadAttribute is the first attribute and is of the form { ... AssignmentExpression }.

If instead it was of the form { es6ObjectLiteral } it would be equally specific and parseable, and it would make tedious passing down of individual attributes a lot nicer…

Note that ES6 syntax has been around for a while now and used a lot, and the fact that {...props} is not an actual ES6 object literal expression is just plain confusing.

wmertens avatar May 06 '16 09:05 wmertens

Right now the spec is that JSXSpreadAttribute is the first attribute and is of the form { ... AssignmentExpression }.

It's not necessarily the first.

RReverser avatar May 06 '16 09:05 RReverser

Oh you're right, I didn't see the ending s on the recursive attribute definition.

All the better, that's nicer to use and it doesn't change the specific parseability, the only thing in a tag that starts with a { is a spreadattribute.

It still looks exactly like an object literal but it sadly and unnecessarily isn't.

wmertens avatar May 08 '16 11:05 wmertens

I keep wishing for this, every time I write intermediate components that just pass props:

<MyComponent {...{key: bar, prop1, prop2}}/>

just seems uglier than

<MyComponent {key: bar, prop1, prop2}/>

Supporting the latter syntax is essentially free, as the former syntax is just a special case of the latter. There are no parsing conflicts, no transpiling inefficiencies, … it just looks and behaves like ES6 and thus reduces astonishment.

wmertens avatar Aug 12 '16 20:08 wmertens

First of all, the object spread operator actually isn't in "es2015" preset proper, but still in stage 2. For teams like mine where we prefer to stick to the established standard for guaranteed maintainable code, the spread operator isn't an option for us yet, and this syntax excludes us.

But also, it really does not make sense for {...{foo, bar}} to work and for {foo, bar} not to. They both end up being {foo, bar} after being evaluated, and if you passed them to any normal function, that function wouldn't (and shouldn't) know how it got evaluated.

JSX is already a series of keys and values, why can't those keys and values be expressed in a normal inline object literal? That would make perfect sense.

jonvuri avatar Aug 13 '16 14:08 jonvuri

Would it at all be possible / make sense to instead have this done without the object literal but still use the idea of shorthand? By that I mean, you could do this

const a = 1;
const b = 2;
...
<Component a b>

I know that currently this actually passes in true to both a and b but I don't know why that's the case.

merlinstardust avatar Aug 29 '16 14:08 merlinstardust

@merlinpatt I think there's a big risk of confusion to do it that way. If you add a property c which is undefined, does that still mean it would be recieved as true? Property names would take of different meaning depending on the contents of the scope they are in.

johnste avatar Aug 30 '16 07:08 johnste

…so, is there any chance of this ever happening? What would it take, a petition, a funny youtube video, Belgian chocolate?

wmertens avatar Aug 30 '16 07:08 wmertens

My perspective on this:

<Foo {item, key}/> (as sugar for <Foo item={item} .../>) seems kind of quirky, but <Foo {item} {key}/> seems reasonably useful for the same reasons as {item, key} was added to ES6. But the syntax is really weird, {} implies an expression but the syntax actually expects an identifier name. <Foo item key /> makes sense in context but that's already taken by boolean-true props. There's also computed property names to consider, <Foo [item] [key]/> should realistically set them to true, which conflicts with how this new syntax would work, which makes understanding the behaviors of these similar looking syntaxes non-trivial.

<MyComponent {...object}/> that we have now makes sense to allow dynamically adding props, especially as you should be able to do <MyComponent {...object1} foobar {...object2}/>. However, <MyComponent {object}/> should explicitly prevent any other props from being added, so the only purpose for that syntax is <MyComponent {foo, bar}/> (two different but equal syntaxes, that's not a good thing) or because you don't like how <MyComponent {...object}/> looks (... is about conveying consistent behavior, that's a good thing). There's nothing obviously beneficial here.

syranide avatar Aug 30 '16 09:08 syranide

<Foo {item, key}/> (as sugar for <Foo item={item} .../>) seems kind of quirky

Why do you feel like it's quirky, @syranide? What if we focus less on this case of property shorthand as a sugar and just talk about this idea: Any object literal expression in JSX sets its key-values as attributes on the element, without worrying about the inner syntax of the literal. We can even think of it as if the object literal attribute syntax is the "natural" JSX syntax since it is more expressive, with the HTML-attribute syntax being the sugar instead:

<MyComponent { prop1: 1, prop2: 'foo' } /> <MyComponent prop1={ 1 } prop2='foo' />

I feel like anyone familiar with JSX wouldn't blink much at the above, and would understand the intent and how it should behave. It also wouldn't be very surprising to then see things like this and understand what's going on:

<MyComponent { [ 'prop' + propIndex ]: 'foo', prop2 } />

And the spread operator is even still supported, with the same semantics:

<MyComponent { ... propList1, ... propList2 } />

( And as a note about your example <MyComponent {...object1} foobar {...object2}/>, there is precedent for properties being specified more than once - if you do that in a normal object literal the last one wins:

{ foo: 1, bar: 2, bar: 3} -> { foo: 1, bar: 3 }

So if we imagine all object literals in one JSX element as just being "concatenated" these would act the same way, with left-to-right merging:

<MyComponent { ... propList1 } { ... propList2 } /> <MyComponent { ... propList1, ... propList2 } /> )

In comparison with this syntax, the current spread-operator syntax is the one that seems odd and less obvious. It's unexpected that these two expressions are not equivalent, when they are equivalent elsewhere in ES2015:

{ foo: 1, bar: 2 } { ... { foo: 1, bar: 2 } }

JSX peeks into the object literal expression and uses its spread operator to instead mean "spread attributes over the JSX element" rather than spreading properties over the object literal, and that behavior isn't very obvious without a prior explanation. And even if it made sense, again, the spread operator is nonstandard in ES2015 and still subject to change, and requires an extra plugin to work.

Also, I'm not sure I understand this, @syranide:

However, <MyComponent {object}/> should explicitly prevent any other props from being added

Why? Why couldn't you mix object expressions and normal attributes? All I said above is assuming that should be possible but perhaps I'm missing something.

jonvuri avatar Aug 30 '16 18:08 jonvuri

@jrajav I was commenting on the two different ideas presented separately.

<MyComponent { prop1: 1, prop2: 'foo' } />
<MyComponent prop1={ 1 } prop2='foo' />

Anyway, both are fine, I even proposed switching to the first one early on, IMHO the most important thing about JSX is that it adds visual clarity to hierarchies, not that it looks like XML. Reusing the native object literal syntax makes a ton of sense technically. However, having both doesn't really make sense. A language isn't about having multiple ways to do the exact same thing based on preference, it's about having a common language to "speak".

Also, worth noting that the object literal syntax may not lend itself as well to spanning multiple lines and in a sense it's also kind of illogical why the braces are in the syntax to begin with, they aren't necessary for anything, they just become fluff from a technical perspective (but that's a separate discussion and there may be reasons why it makes sense):

<MyComponent {
  prop1: 1,
  prop2: 'foo'
} />
<MyComponent
  prop1={ 1 }
  prop2='foo'
/>

I think even the spread syntax that JSX has now is kind of weird, but it's probably the only really logical syntax for it given the current syntax of JSX. If <Foo bar/> was actually an alias for <Foo bar={bar}/> then <Foo ...object/> would make sense for the spread syntax and JSX would feel more coherent.

In comparison with this syntax, the current spread-operator syntax is the one that seems odd and less obvious. It's unexpected that these two expressions are not equivalent, when they are equivalent elsewhere in ES2015:

If the syntax was <Foo bar=[1]/> instead then the spread syntax IMHO would be <Foo [...object]/>, hence {} is about expressions and not object literals (but that's debatable). Anyway, so <Foo {...foo} bar={1}/> is currently translated {...foo, bar: 1}, i.e. <Foo {#} bar={1}/> translates to {#, bar: 1}. Now you can extend this with multiple values and key-value pairs too as you're suggesting, but we already support both of those features with the current JSX syntax, so we shouldn't add another syntax for it where the only reason to pick one over the other comes down to personal preference = not a good feature to add.

In summary, I largely agree that the object literal syntax is preferable in many ways, practically and technically, but that syntax explicitly competes with a syntax already decided on by JSX. It makes sense having competing languages implementing alternative syntaxes. It does not make sense having it in JSX, JSX is an explicit choice with an opinionated syntax. I can also imagine a few additional tweaks to the syntax that would further make it less XMLy and more JSy, which may make sense given that HTML is far from the only frontend out there.

PS. This is my opinion/perspective and may not be shared by everyone else.

syranide avatar Aug 31 '16 08:08 syranide

@syranide A lot of what you're saying makes sense and I can see where you're coming from. I do support the idea of having one way to do something, where it makes sense.

Just to reiterate what my main pain point is, it's less to do with the spread operator that you focused on (which I honestly don't care much about, I prefer explicitly passing each property for a robust interface). It is more that there isn't a non-ugly way to apply local symbols to an element without repetition. There are only two alternatives:

<MyComponent foo={ foo } bar={ bar } /> Repetition <MyComponent { ... { foo, bar } } /> Ugly, stage 2, requires explanation

Allowing arbitrary object literal expressions would solve this neatly and also enable other useful patterns like computed keys.

As for the notion of competing syntaxes, that's one way to look at it. But another way to look at it as JavaScript vs. the JSX sugar for the JavaScript. Elsewhere in JSX, { } means "Exit back to JavaScript here and compose with it in a sensible way." Couldn't it mean the same thing in this case? Allow JavaScript interpolation for props as well and not just children, with the resulting object value being concatenated at that point in the eventual props object. Basically, just a way to have more direct access to the props object inline in JSX. And because it's plain JavaScript, it's more powerful and expressive. JSX composing so well with JavaScript is one of its main strengths, but attribute lists are one of the missing pieces currently, when they could easily benefit from the same. And from this perspective, the key='value' syntax for attributes is just a sugar for adding props to that object with more HTML-like semantics, not a competing syntax, the same way that there's no competition for a <div>{ list.map(() => { ... }) }</div> because JSX lacks the expressiveness for it.

Granted, this would make more sense if the syntax was instead <MyComponent { { foo: 1, bar: 2 } } />, but that would still be an improvement in my opinion.

jonvuri avatar Aug 31 '16 14:08 jonvuri

It is more that there isn't a non-ugly way to apply local symbols to an element without repetition. There are only two alternatives:

@jrajav Yeah, if that's what you want then spread really isn't a good fit, no doubt. But I'd say the proposed syntax seems like an overreach for trying to accomplish just that. So in my opinion, as I said, I think the real solution would be to remove boolean-true props i.e. no more <Foo thisistrue/>, that would allow us to provide an element prop syntax that is functionally on par with object literals. I personally would probably find that preferable, but I imagine that not everyone agrees, especially considering the current legacy (although that is codemod-able). It comes down to choosing which of the two features you want, it seems you can't have both and have a truly familiar and coherent syntax. Both aren't a necessity either, languages have strengths and weaknesses, it's about balance.

Going off on a tangent, if we imagine a future where ECMA wants to incorporate these "elements" into JS, then it's important to remember that they may chose a very different version of JSX. The JSX we're discussing here is simply one possible proposal in that scenario. But if this JSX were to be seriously considered it also has to be "rigorously designed", adding small nice-to-have features with large-scope syntax changes/side-effects seems like a quick way to shut that door, there needs to be a coherent vision for the language, that includes a balanced trade-off.

Elsewhere in JSX, { } means "Exit back to JavaScript here and compose with it in a sensible way." Couldn't it mean the same thing in this case?

IMHO, that's the wrong way to look at it, "JSX-elements" is a true citizen of this language, there is no exiting back to JS. Expression values can be JSX-elements and JSX-element property values can be expressions. JSX-elements is part of the language grammar like everything else, it's not special, just more syntax rules.

Put differently, if I would design JSX I would design the syntax to more along the lines of the following, to be more comparable to JS syntax.

<Foo
  booleanValue=true
  numericValue=1
  stringValue="bar"
  valueByPropName
  ...spreadObject
/>

My overall point is; consider if you made the same syntax extension to the current JS object literal syntax, adding a separate but equivalent syntax to achieve almost exactly the same thing and a nice-to-have-feature. It doesn't seem like a good idea.

syranide avatar Aug 31 '16 16:08 syranide

@syranide I agree with you that the syntax should not become overly sugary and complex, but our point is that the current syntax is a subset of the proposed syntax, given the stage-2-but-ready-for-stage-3 object spread operator.

That is to say, <MyComponent { ... { foo, bar } } /> behaves exactly the same in this proposal, but <MyComponent { foo, bar } /> becomes possible, using language constructs that are already solidly in place.

Furthermore, adding JSX syntax to ECMA seems improbable, and if it happens in has only 2 outcomes:

  1. The syntax is JSX => whatever we have then is the standard
  2. The syntax is not JSX => we will need to transpile like before

So I don't think this possibility should inform this decision.

For me it is clear-cut: Make JSX more obvious at 0 cost, or not.

wmertens avatar Sep 07 '16 12:09 wmertens

Yeah @wmertens, that's still a pertinent point - again, it would make a lot of sense to simply support any sort of object literal expression since that's what the current syntax looks like anyway and it would be perfectly compatible with it. Nothing crazy is even being added, just making the current implementation make more sense.

And as for trying to do this in the "native" property-value syntax as you're proposing @syranide, I just want to point out that that would interfere slightly with applying attributes to the elements, for instance disabled, in the same way you'd apply them to native HTML elements, if you are using the same name in scope. With the object literal syntax it would be more obvious what the intent is, and would reduce the risk and annoyance of possible shadowing.

jonvuri avatar Sep 07 '16 15:09 jonvuri

Put differently, if I would design JSX I would design the syntax to more along the lines of the following, to be more comparable to JS syntax.

<Foo
  booleanValue=true
  numericValue=1
  stringValue="bar"
  valueByPropName
  ...spreadObject
/>

As @jrajav pointed out, someone with a background in HTML would expect valueByPropName to be a truthy boolean here...

I think the problem here is that JSX too much tries to look like HTML. If it were more obvious that JSX is more JS rather than HTML... For example (note colons and commas):

<Foo
  booleanValue: true,
  numericValue: 1,
  stringValue: "bar",
  exprValue: x + y,
  valueByPropName,
  ...spreadObject
/>

The only problem I can see with the above syntax is that the closing bracket > might be confused with the greater than operator: <Foo booleanValue: bar>baz></Foo>, but the parser probably could raise an error in such case, and the user could fix it with extra (): <Foo booleanValue: (bar>baz)></Foo>, or a trailing comma: <Foo booleanValue: bar,>baz></Foo>. The fix is ugly, but probably would not be needed often. with the named character entity gt: <Foo booleanValue: bar>baz&gt;</Foo>.

denvned avatar Sep 07 '16 20:09 denvned

Not much input on this for a while... would a pull request be welcome here?

jonvuri avatar Oct 12 '16 18:10 jonvuri

The problem here is that one side (contra this issue) believes that {[object literal]} confuses the syntax and {...{[object literal}} is good enough, and the other side (pro) can only point to a stage-3 ES2017 feature (object spread operator) and say it's more logical. So it's pragmaticism vs purity.

It's not a missing PR issue, we have the technology.

On Wed, Oct 12, 2016 at 8:44 PM Jonathan Rajavuori [email protected] wrote:

Not much input on this for a while... would a pull request be welcome here?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/facebook/jsx/issues/23#issuecomment-253301931, or mute the thread https://github.com/notifications/unsubscribe-auth/AADWlg3bgu-9c8_CykTBXArSe_Trh1T7ks5qzSqFgaJpZM4DBkOp .

wmertens avatar Oct 12 '16 20:10 wmertens

I'm in general supportive of this general feature. I think we should probably just remap the meaning of <Foo x />. See #65

sebmarkbage avatar Oct 12 '16 20:10 sebmarkbage

What do you think of limiting the scope to a single identifier inside curly braces ({myProp}) that is equivalent to myProp={myProp}?

For example:

<Comp {...obj} {value} flag />; // same as <Comp {...obj} value={value} flag={true} />

Which would become...

React.createElement(Comp, _extends({}, obj, { value: value }, { flag: true }));

If you want to specify multiple, do them in separate curly brace instances:

<Comp {a, b} c="c" /> // invalid syntax
<Comp {a} {b} c="c" /> // :)

If you need to rename the prop, just do so using already-existing jsx syntax:

<Comp {a: aWithAnotherName} /> // invalid syntax
<Comp aWithAnotherName={a} /> // :)

If I'm not mistaken, this would not be a breaking change.

spudly avatar Apr 05 '17 12:04 spudly

Here I have to agree with @jrajav , as the most logical for one such as me (with relatively little experience in react) would be to write

<Comp {{ propName, otherProp: 2 }} /> 

Since it is following the conventions of every other interaction between JS and JSX. The first curly braces mean here begins JS code, and I want to output an object literal, which can be used to build the properties that are passed to Comp. This also means that

<Comp { ...otherProps } />

should be deprecated (but still supported for backward-compatibility) in favor of

<Comp {{ ...otherProps }} />

Which is tiny bit more verbose but follows the convention and is easy to parse (as it is just a JS expression).

zinkkrysty avatar Jun 10 '17 17:06 zinkkrysty

@zinkkrysty Your second example would wrap the spread with an object and cause breaking changes and an unnecessary flatten. @spudly's syntax would indeed produce no real change and just be an extension to the underlying api

pkrawc avatar Jun 20 '17 15:06 pkrawc

Event handlers would be glad to get some love too. I suggest <button {this.onClick}/> to be equal to <button onClick={this.onClick}/>. And <button {this.props.onClick}/> to be equal to <button onClick={this.props.onClick}/>.

garkin avatar Aug 03 '17 04:08 garkin