jsx
jsx copied to clipboard
support object literal property value shorthand
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:
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}} />)}
this doesn't seem to work so far
You probably didn't enable ES6 transformation in your transpiler.
oh, sorry, I actually did for this test.
but is it ~really~ logical that {...{foo}}
is supported when {foo}
isn't?
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.
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.
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.
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.
Right now the spec is that JSXSpreadAttribute is the first attribute and is of the form { ... AssignmentExpression }.
It's not necessarily the first.
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.
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.
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.
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.
@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.
…so, is there any chance of this ever happening? What would it take, a petition, a funny youtube video, Belgian chocolate?
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.
<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.
@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 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.
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 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:
- The syntax is JSX => whatever we have then is the standard
- 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.
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.
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: with the named character entity <Foo booleanValue: bar,>baz></Foo>
. The fix is ugly, but probably would not be needed often.gt
: <Foo booleanValue: bar>baz></Foo>
.
Not much input on this for a while... would a pull request be welcome here?
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 .
I'm in general supportive of this general feature. I think we should probably just remap the meaning of <Foo x />
. See #65
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.
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 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
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}/>
.