facebook / jsx

The JSX specification is a XML-like syntax extension to ECMAScript.
http://facebook.github.io/jsx/
1.96k stars 133 forks source link

JSX 2.0 #65

Open sebmarkbage opened 7 years ago

sebmarkbage commented 7 years ago

We have accumulated a number of these nice-to-haves breaking changes that would be nice to incorporate. However, at this point JSX is a hugely adopted syntax in all kinds of tooling. Making breaking changes will churn that ecosystem significantly. I think it is probably only worth doing as a batch when the accumulated amount of changes makes it worth while.

Let's call it JSX 2.0.

What should go into it?

Since a completely incompatible version of JSX has landed in Reason it would be good to find a way to unify them.

I'd say at least we'd want:

Some more controversial/problematic ones that could plausibly be fixed:

What else?

cc @jordwalke @SanderSpies

syranide commented 7 years ago

I would be interested in having a real story for comments e.g. #7. {/**/} feels like a hack, and kind of also is as it affects the children when used in certain places. If we actually end up going with #35 (bold, I like it :)) then just properly supporting regular comment syntax is a no-brainer IMHO.

syranide commented 7 years ago

Also, having an actual syntax for fragments would be a very welcome feature (e.g. the previously talked about <>).

caub commented 7 years ago

what would it be for fragments?

const frag = <>
    <div>hello</div>
    <div>world</div>
</>;
// vs 
const frag = [
    <div>hello</div>,
    <div>world</div>
];

then it's the same, you'll need {frag} to insert it. I think arrays are good enough, similar to DOM fragments, and more powerful to manipulate

comments is a good idea, <!-- --> like html could be fine, but would they be inserted in the DOM (I guess no)? would it conflict with how react uses comments sometimes for text nodes I think

theduke commented 7 years ago

but would they be inserted in the DOM?

There should be a setting to turn it on for dev / off for production in the implementing libraries.

sbussard commented 7 years ago

We need more restrictions on how JSX can be written. People are using terrible practices and teaching it to others. Example: child props should only be passed into components via childProps, otherwise it's a mess. There are so many ways to do things but few even ask if they should do it that way.

mohsen1 commented 7 years ago

Something like Angular ng-if would be nice to have in JSX. Making rendering of an element conditional is not easy in JSX. Maybe adopt Angular 2 *if

tachang commented 7 years ago

Most of these changes wouldn't affect my codebase since I use them sparingly. Comments however would be great. It's kind of insane that normal javascript commenting isn't supported even with all the crazy tool chains we are running.

jasonslyvia commented 7 years ago

I'd say allow adjacent elements without enclosing tag, really hate additional <div>s like this:

<div>
   <ComponentA />
   <ComponentB />
</div>
syranide commented 7 years ago

@jasonslyvia That's what fragments are for.

alistairhutton commented 7 years ago

Would be nice to have something like how Adobe/Apache Flex deals with conditional inclusion of components. It is refered to as state in Flex world. You can assign a child component a state name and then when you set the parent components state it hides/shows child components as appropriate

oleksandr-shvets commented 7 years ago

Hi guys! What do you think about this idea? https://github.com/alexander-shvets/cascading-component-layers-react

jasonslyvia commented 7 years ago

@syranide It would be better if it could be solved in syntax level.

lacker commented 7 years ago
<if {myCondition}>
  <div>This part only gets shown if myCondition is true</div>
</if>
BrendanFDMoore commented 7 years ago

@mohsen1 our convention for our stateless functional components is to use:

import MyInnerComponent from '../my-inner-component';
function MyOuterComponent({ isInnerComponentIncludedBool }) {
  ...
  return (
    <div>
      <SomeOtherComponent />
      {
        isInnerComponentIncludedBool
        ? <MyInnerComponent />
        : null /*Or some fallback component / messaging*/
      }
    </div>
  );
}

which works pretty well for us. It's pretty clean as long as your outer component props are a clear pre-calculated boolean for show/hide. Because your components don't have any business logic, this should be fine already, right? ;)

edit: the ternary allows us to include an alternative, which we commonly do want, without needing to add an if !someBoolFlag check also.

kevinsimper commented 7 years ago

@mohsen1 @lacker @BrendanFDMoore

You can make it as easy as angular if like this, so I don't see any need for an if tag

{ somethingTrue &&
  <div>Will only show if somethingTrue is true</div>
}
mohsen1 commented 7 years ago

@BrendanFDMoore I'm aware of those workarounds and we're using them. Since JSX is syntax sugar for React.createElement adding more syntax sugar won't hurt.

In your example the syntax can look like this:

return (
    <div>
      <SomeOtherComponent />
      <MyInnerComponent *if={isInnerComponentIncludedBool} />
    </div>
  );

I guess mentioning Angular earns you lots of downvotes here!

BrendanFDMoore commented 7 years ago

@kevinsimper True, it's more compact. We stick to the other convention because instead of null we often want to show some placeholder text or replacement component.

@mohsen1 you're right, there's no harm in more helpful sugar to clean up unpleasant syntax where there is wide interest and a commonly accepted pattern. I'm not necessarily against the addition of an if helper.

nkohari commented 7 years ago

I find that assigning optional components to variables helps for clarity, for example:

function Profile({ user }) {
  let avatar;
  if (user) {
    avatar = <Avatar user={user} />;
  }
  return (
    <div>
      ...whatever the outer component represents...
      {avatar}
    </div>
  );
}

While it would be nice to have direct support in JSX for conditional components, I'm really wary of adding things like <if> or an if=... attribute. Next people will want <for> and <while>... IMHO there's no need to replicate that much JS functionality in JSX.

fab1an commented 7 years ago

Automatically bound arrow function that don't allocate a new function everytime:

<div onLoad={@this () => doSomething(blabla)}/>
max-mykhailenko commented 7 years ago

IMHO jsx has only several problems:

JS should have separate context in curly braces.

meznaric commented 7 years ago

With current syntax {something && <Component />} when you render a more complex component this introduces another level of indentation. Which in my opinion helps us to spot conditional logic, but does not look as nice.

I think that having multiple ways of doing the same thing adds to overhead when reading the code. It's small but noticeable.

jmar777 commented 7 years ago

Make whitespace handling more consistent with HTML: https://github.com/facebook/react/issues/4134 (sorry for brevity, currently mobile...).

bjrmatos commented 7 years ago

@mohsen1 "It's just JavaScript, not a template language" -> no need to replicate JS functionalities with custom syntax. That is the main benefit of JSX IMO, seriously is so easy to do this with js even if it looks "weird" (for me it is not weird, it is just the syntax of the language)

fdecampredon commented 7 years ago

I would like to be able to specify some property value with tag :

<SplitContainer
  left={<div>...</div>}
  right={<div>...</div>}
/>
// vs
<SplitContainer>
  <.left>
    <div>...</div>
  </.left>
  <.right>
    <div>...</div>
  </.right>
</SplitContainer>
Pajn commented 7 years ago

@mohsen1 It has nothing to do with mentioning Angular but with introducing additional syntax that makes it harder to learn and harder to read without adding any benefit as it's more characters to type than {condition && VDOM} and it's harder to understand (&& will return the lhs if it's falsey as all JS, what will *if do? return null? undefined? rip out the element completely as it was never there?)

Also with #35 and #39 you could do

if (condition) {
  VDOM
}

wich would be JS instead of some special thing

mohsen1 commented 7 years ago

@Pajn That looks much better than *if or anything like that. Also #35 is a great proposal! 👍

nkkollaw commented 7 years ago

Don't fuck it up like Google did with Angular 2, keep the thing compatible with older versions...

NekR commented 7 years ago

I highly doubt that it makes sense to have full backwards compatibility for JSX 2.0. If we are going to improve it -- we should improve.

After all, no one is forced to migrate to JSX 2.0 and you still will be able to write your React apps in JSX 1. React elements/components are just JavaScript calls, so there is no matter what you are using. In some case, if you would like, you probably will be able to use JSX 1 and JSX 2.0 in the same project, e.g. for migration. Just apply new transform for new components.

IMO, that's it.

NekR commented 7 years ago

Though, I'm not a fun of adopting features from Angular. Like some already here proposed are already possible with JSX + React.

brochington commented 7 years ago

I would like more granular control over what function the JSX is compiled to.

/** @jsx myImageFunc img*/

import React from "react"
import myImageFunc from "./somewhere"

SomeClass extends React.Component {
     render() {
        return (
            <div> // compiled to default, ie. React.createElement
                <img src={...}/> // compiled to myImageFunc
            </div>
        )
    }
}
oleksandr-shvets commented 7 years ago

@brochington you can do it in that way:

import myImageFunc as Img from "./somewhere"
     render() {
        return (
                <Img src={...}/> // compiled to myImageFunc
        )
    }
brochington commented 7 years ago

@alexander-shvets Those aren't the same.

In my example img would compile to myImageFunc(class/string, props, ...children). In your example Img would compile to React.createElement(Img, props, ...children).

oleksandr-shvets commented 7 years ago

@brochington why myImageFunc is better than [Stateless Function Component](react pure functional components) for you?

brochington commented 7 years ago

@alexander-shvets Your link doesn't seem to work for me, but I assume you are referring to This. The JSX still uses React.createElement. I would like to be able to control this.

Pajn commented 7 years ago

@brochington Why not just write that as myImageFunc(class/string, props, ...children)? The point of JSX is basically to hide the repeated createElement call so if you want to call another function just once, write is as that. It would both be shorter and less surprising than a single tag that behaves differently from all the others.

oleksandr-shvets commented 7 years ago

@brochington you can do it using custom jsx pragma: /* @jsx myImageFunc /

brochington commented 7 years ago

@alexander-shvets That wouldn't work either, as both the <div /> and the <img /> would then use myImageFunc. Please notice the two arguments to my pragma definition above.

@Pajn JSX can and is used in other forms outside of the context of React. JSX is a way to define syntax sugar. In the case of React part of that sugar is hiding the createElement method. But their is no reason why this needs to be the case. Just as @alexander-shvets has pointed out, you can already define JSX compilation via pragma definitions. I just want a more granular approach to this.

oztune commented 7 years ago

I really like the elegance and subtle benefits of #35.

I think the ability to mix JSX 1 and 2 in the same project will be a high priority for those of us maintaining large code bases.

rauchg commented 7 years ago

@lacker I'm curious why you prefer that over do expressions? Seems a more elegant, "pure JS" solution

rauchg commented 7 years ago

Obviously there's a quick declarative win (specially for analysis), but for most situations I think we just want a escape from the && hack and ? / :

zertosh commented 7 years ago

Request: Syntax to declare properties associated (but not belonging) to a child for consumption by the parent. I don't have opinions on the syntax, but here's an example, that borrows from the "fragment" syntax:

<Layout>
  <{grow={1}}/>
  <Text />

  <{shrink={2}}/>
  <Image />

  <Video />
</Layout>

Above I'm saying that Text has grow=1, and Image has shrink=2, but those props are not for Text and Image, but for Layout. The way to do this in JSX 1.x is to use wrapper elements:

<Layout>
  <Item grow={1}>
    <Text />
  </Item>

  <Item shrink={2}>
    <Image />
  </Item>

  <Video />
</Layout>

But that's really verbose.

cc: @adamjernst

coreh commented 7 years ago

IMO, the main downside of do expressions is the triply nested indentation level they require

<div>
  {
    do {
      if (condition) { 
        <Foo />;
      } else {
        <Bar />;
      } 
    }
  }
</div>

Even if you make the indentation more compact, it still doesn't look particularly good:

<div>
  { do { if (condition) { 
    <Foo />;
  } else {
    <Bar />;
  } } }
</div>

I like the idea of { } implicitly starting in a do context, so you could do:

<div>
  {
    if (condition) { 
      <Foo />;
    } else {
      <Bar />;
    } 
  }
</div>

Or, with the more compact indentation:

<div>
  { if (condition) { 
    <Foo />;
  } else {
    <Bar />;
  } }
</div>

If we're going to add support for () in attributes, these could always start in an expression context.

gravitypersists commented 7 years ago

Sorry I don't have a concrete suggestion, but I feel as though JSX is trying to solve too much (and adding control statements will just add more).

Currently, JSX solves two concerns, one is structure (akin to DOM structure) and the other is for assigning props to elements. I'm not sure how, but is there a way to separate these concerns?

kevinSuttle commented 7 years ago

Funny you should ask: http://kevinsuttle.com/posts/jsx-2-adobe-flex

kevinSuttle commented 7 years ago

But seriously, immutability.

jmar777 commented 7 years ago

While I don't care for the attribute-based conditional rendering proposal above, I did think it's worth highlighting a couple pain-points with relying primarily on { condition && <Foo /> }:

1. While often suggested as an alternative to if() {}, it doesn't work with all falsy expressions:

For example, consider the following:

{(users && users.length) &&
  <ul>
    {users.map(user => <li>{user.username}</li>)}
  </ul>
}

Unless you've run into this specific issue, it may not obvious why this code is wrong. This code works fine if users is undefined or null, and it works if users is non-empty, but if users is an empty array, then it actually renders a 0. Ultimately this makes sense (React obviously has to let curly-brace expressions emit numerical values), and the workaround is easy, but it's an easy gotcha, nonetheless. Especially when we advocate this syntax as the alternative to first-class if(){} statements, it's easy to see how this bites newcomers in particular.

2. It doesn't play nice with conditionally rendered sibling components:

Please note that this is not a syntax proposal, but instead of having something with "block-level" support, similar to this:

[if (condition)]
  <Foo />
  <Bar />
  <Baz />
[/if]

We instead need:

{ condition && <Foo /> }
{ condition && <Bar /> }
{ condition && <Baz /> }

If the condition itself gets more complex, then the code gets uglier, or alternatively we need to store the result to a temporary variable, which further increases the verbosity. I will note that this particular pain point would be eased by better support for fragments.

TBH I've never seen a specific proposal that I'm overly fond of, but inasmuch as branching logic is integral to all but the most trivial of rendered outputs, I think there's a compelling case to be made for first-class support in JSX.

jkarttunen commented 7 years ago

Somehow support cleaner, simpler looking syntax like jade/stylus so that co-creation with non-developers gets easier

kevinSuttle commented 7 years ago

Supporting <style> tags inside components.

mstijak commented 7 years ago

I would love to be able to use . in attribute names, similar to Aurelia. JSX syntax doesn't allow it. <input type="text" value.bind="firstName">.

In Cx, xml namespaces are used for this purpose, but it feels a little bit hacky. <input type="text" value:bind="firstName">.

kemurphy commented 7 years ago

Fragments would be super useful. One way I've thought about going about it is using the spread operator in child context:

const frag = [
  <div />,
  <div />
];

return (
  <div>
    {...frag}
  </div>
);

One alternative could be using a self-closing tag instead of curlies for the spread (<...frag />).

Bonus points if sibling tags at the top level of the expression could implicitly create a fragment so we could avoid the trailing commas on the elements in the fragment array. This also adds support for multiple conditional children for free, since it's the same syntactic construct:

const frag = (
  <div />
  <div />
);

<div>
  { shouldShowChildren &&
    <div />
    <div />
    <div />
  }
</div>