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

[Feature request]: Add new syntax for jsx classes #155

Closed
MaxmaxmaximusGitHub opened this issue Apr 13, 2020 · 40 comments
Closed

[Feature request]: Add new syntax for jsx classes #155

MaxmaxmaximusGitHub opened this issue Apr 13, 2020 · 40 comments

Comments

@MaxmaxmaximusGitHub
Copy link

MaxmaxmaximusGitHub commented Apr 13, 2020

I remind you that JSX itself is a SUGAR on top of javascript ;)

💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡
💡 We need more sugar! New syntax for jsx classes! (and isolated styles)
💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡💡

  • Start Date: 2020-04-13

Summary

new JSX class syntax:

  1. attr start with dot is class name (intentionally similar to css, yes)
  2. if this attr have expression value, then if exp is true, then class exists, otherwise not exist
  3. of course, these classes have an ISOLATED SCOPE, for the ISOLATION of styles, I came up with this syntax.

Basic example

this:

<div .block .chat .active={ state.active }></div>

compile to:

<div className={ `block chat${ state.active ? " active" : "" }` } ></div>

by the way, no one forbids you to add dynamic class names, and still use your favorite css modules

this:

<div .[css.button] .[css.active]={ user.active } ></div>

compile to:

<div className={ `${css.button}${ user.active ? " " + css.active : "" }` }></div>

Motivation

Attributes names with started a dot symbol is prohibited, so dot is idle idle. dot can be used for syntactic sugar for routine 90% of the operations of assigning classes to elements.

people will be able to write a main class describing what kind of entity it is. and add dynamic modifier classes, which reflect the component state.

it’s just syntactic sugar. but it will save 90% of the time for developers. the programmer simply type on a keyboard the dot symbol, and the IDE will automatically autocomplete them for the available classes.

in brevity and elegance, this syntax is even superior to html ;)

our beloved jsx is high time to develop, because competitors (angular and vue) do not stand still ;)

Detailed design

you just look how cool:

<ul .users>
  {this.props.users.map( user =>
      <li .user .banned={ user.isBanned } .admin={ user.isAdmin }>
        User { user.name }
      </li>
  )}
</ul>

and imagine how it will be cool with syntax highlighting in the IDE and if you click by mouse wheel you will immediately be transferred to the desired selector in css file.

what it looks like now:

<ul className="users">
  {this.props.users.map( user =>
      <li className={ `user${ user.isBanned ? " banned" : "" }${ user.isAdmin ? " admin" : "" }` }>
        User { user.name }
      </li>
  )}
</ul>

feel the difference?

this:

<div .box .active={active} onClick={toggleActive}></div>

compile to this:

<div className={ `box${ active ? " active" : "" }` } onClick={toggleActive}></div>

just compare

export default function TabTitle({active, onClick, children}) {

  return <div className={ classes('tab-title', {
    __active: active
  }) } onClick={ onClick }>

    { children }

    <style jsx>{ style }</style>
  </div>
}

and my version:

export default function TabTitle({active, onClick, children}) {

  return <div .tab-title .__active={active} onClick={ onClick }>

    { children }

    <style jsx>{ style }</style>
  </div>
}

is a syntax hell :

image

We can use new syntax and old syntax at the same time:

this:

function classes(classesArr){ classesArr.join(' ') }

<div .box .active={active} className={ cssModule.class + classes(['one', 'two']) }>

compile to:

function classes(classesArr){ classesArr.join(' ') }

return <div className={[cssModule.class + classes(['one', 'two']), `box${active ? ' active' : '' }`].join(' ') }>

Drawbacks

Why should we not do this? Please consider:

There are no drawbacks, since the dot symbol was previously prohibited, backward compatibility is present.

The disadvantage is that jsx with the new syntax will cause a syntax error in compilers that do not support the new function. therefore, we must write babel-plugin to translate the new code into the "old" jsx.

Alternatives

You can write the babel plugin to support the new syntax, but this will not motivate the creators of the IDE to add new syntax. The popularity of React and Facebook and the headline "React 17.0 - New Syntax for Classes." will motivate support this feature.

Adoption strategy

React competitors use much more convenient syntax than jsx. Since it uses attributes starting with special characters. jsx does not use such attributes, and simply prohibits them. People are tormented by class definitions in jsx and rejoice in Angular and Vue. People will be happy with the new “meaningful” syntax, which will be much more organic than native html. Since in css classes start with a dot, in jsx they will also start with a dot. And also the most common operation with classes is adding or removing a class, depending on the state. We add and make it native. There is not a single developer in the world who says "bliiin, well, support for convenient classes has appeared (" and there are 99% of people who say "FINALLY" =)

How we teach this

This innovation is sugar, and does not break or change existing behavior. Therefore, we can simply ADD a new section to the "dot classes" documentation. Those who like the new syntax will use it, those who like the old will use the old. Anyone who likes both syntaxes can combine them. className is just a string, so at the jsx compilation stage we can quietly unify both methods of class declaration.

Unresolved questions

Since we are doing such a thing, then maybe we should immediately think about isolated styles for the component? Since we are making syntax changes, it may be that we think over the isolation of styles in advance? After all, if we implement this without thinking it over, then we will not be able to change it on the future. I suggest thinking about how to natively (from the point of view of jsx) and syntactically solve the problem of encapsulating classes, and how to make it flexible enough so that other encapsulation libraries can USE this syntax.

by the way, we can also add dynamic attribute names

this:

let prop = "ololo"
<div [prop]="value"></div>

compile to:

let prop = "ololo"
React.createElement('div', { [prop]: "value"} )

plus this syntax allows you to finally do this:

function List(props) {
  return <ul .list {...props}></ul>
}


function Component() {
  return <List .red></List>
}

its render to:

<ul className="lisit red></ul>

ability at the precompilation stage to competently concatenate dot classes and className is phenomenal =)

Adequate guys, do you want me to write a transpiler plugin for this syntax, and add support for it in your IDEs? Who agrees? I think to create a separate repository =)

UPD:

cheers guys, I came up with a workaround !!!! thank god these guys did not forbid the dollar symbol !!! =)

<div $chat $active={ false } $opened={ null }></div>

I will write a babel plugin that implements the behavior that I described here. the dollar sign will temporarily act as a dot symbol =) this way we can test new syntax

yes, it breaks backward compatibility, since now you can’t use properties starting with the dollar sign, but it's not my fault, but the guys from React =)

but living even that way will be much easier. of course it's a fierce crutch, but at least something

p.s. yes and goodbye typescript

I experimented a lot, and technically, the length of such a syntax is literally equal to the length of the syntax I proposed:

image

but visually the dollar symbool is annoying and far less logical than the dot symbool.

to implement override and disable classes semantics, I will write the runtime mergeProps function, but this will be the next step

@foray1010
Copy link

I downvoted this because:

  1. It doesn't stick to HTML syntax, it is hard to understand for new comer
  2. It is impossible to type it in flow or typescript as it is a dynamic prop name

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 23, 2020

@foray1010

It doesn't stick to HTML syntax, it is hard to understand for new comer

  1. it follows the html syntax, since ANY characters in attribute names are allowed in html.
  2. jsx also does not adhere to the html syntax since in html you need to write a class and not className
  3. jsx itself does not adhere to the html syntax since in html the value of attributs should be framed in quotes, and jsx allows the expression attr = {2 + 3}
  4. The new syntax will not cause confusion among the beginner, but will increase the understanding of beginners, since it is superior to html and has syntactic integrity with css.
  5. we don’t remove the old className syntax, we just add standardized sugar on top of it, which will improve static analysis, code autocompletion, and the way to define classes. =)
  6. in general, we can add dynamic classes, but this contradicts the ideas embodied in this proposal
<div .user .user_profile_{this.state.user.role}_role ></div>

although it looks quite comfortable ;)

It is impossible to type it in flow or typescript as it is a dynamic prop name

purpose, sentences, to make class names static, not dynamically, therefore, you are completely diametrically misunderstood this sentence =)

@foray1010
Copy link

Did u use typed language before? we need to predefined attribute types before use

@mAAdhaTTah
Copy link
Contributor

@foray1010 This new JSX syntax would transpile down to className, so the TS/Flow issue wouldn't be an issue.

That said, JSX isn't DOM-specfic, so I'm not sure how acceptable new syntax that targets className specifically would be here.

@j-f1
Copy link

j-f1 commented Apr 23, 2020

I dislike this because it favors one means of styling (CSS using global class names) over all others (CSS modules, inline styles, CSS-in-JS, React Native, etc.) for no clear reason, and blocks JSX from ever using this syntax for something else in the future.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 24, 2020

@j-f1 no, this syntax is CREATED for isolated and SCOPED class names, you completely did not understand the idea. I, using this syntax in my framework, work with a bang https://www.npmjs.com/package/ui-js

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 24, 2020

@foray1010

Did u use typed language before? we need to predefined attribute types before use

dot classes are not html attribute names. you just did not understand the idea. dot classes these are the lines that fall into the className property, when compiling jsx

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 24, 2020

@mAAdhaTTah

That said, JSX isn't DOM-specfic, so I'm not sure how acceptable new syntax that targets className specifically would be here.

now attributes starting with a period are generally prohibited. no platform uses them. There are no conflicts with other platforms, just a bonus for the web platform. benefit is - there is no harm.

moreover, one can simply introduce the agnostic concept of a component class. and React for web will implement it as a css class. and React Native will implement it as something else for example.

image

@mAAdhaTTah
Copy link
Contributor

Unless the feature is broadly useful, I don't see this being accepted as part of JSX. You implicitly recognize there's no use for it in React Native, and there is also no clear use for it in other rendering targets like canvas, webgl, & the terminal, all of which have React renderers. New syntax that's only useful in one case doesn't really clear the bar for adding it.

@dantman
Copy link

dantman commented Apr 24, 2020

@j-f1 no, this syntax is CREATED for isolated and local class names, you completely did not understand the idea. I, using this syntax in my framework, work with a bang https://www.npmjs.com/package/ui-js

Your first post gives this example:

<div .block .chat .active={ this.state.active }></div>

its equivalent for

<div className={ `block chat${ this.state.active ? " active" : "" }` } ></div>

How are these global class names "isolated and local class names"?

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 25, 2020

@mAAdhaTTah

Unless the feature is broadly useful

it is very useful, since now it is inconvenient to make dynamic classes based on variables. and this function will make it convenient. this function will save 40% of the time during layout. Is saving 40% of the time in layout not a useful feature?

@dantman

How are these global class names "isolated and local class names"?

These are not global class names, these are local class names. the programmer should not be involved in the manual addition of namespace, so you mistakenly assumed that these are global class names.

Next, I wrote:

  1. Now that we have a static definition of class names, we can ANALYZE them at the stage of compiling the code, and we can add namespaces. In other words, the syntax proposed by me makes the work of transpilers that add namespaces and isolation systems to classes easier. Since I came up with a STANDARD for class descriptions.

Then I wrote:

  1. Since we made the class description declarative and created a special syntax for it, we can also come up with a class isolation system and build it into the react, or leave this to the Babel type transporters.

You just did not understand my PHENOMINAL idea, like everyone who put dislike. Their programming level is fundamentally low compared to mine. Therefore, they are not able to realize such a revolutionary breakthrough and useful approach. They are still young, learn later.

speaking at the level of ordinary people, and not at my transcendental level of genius:

  1. all current style isolation tools will work as before

  2. new and tools for isolating styles can add insulation more conveniently

Example:

  1. we write loader on webpack which scans jsx file

  2. we import style.css file into it

  3. in jsx we write

style.css:

* { font-size: 50px }
.box { color: red }
.box.active { color: green }

component.jsx:

import style from 'style.css'
import {useState} from 'react'


export default function Component() {

  const [active, setActive] = useState(false)
  function toggleActive(){ setActive(active => !active) }


  return <div .box .active={active} onClick={toggleActive}>

    // apply ISOLATED style
    // this tag WILL REMOVED by BABEL !!!! OF COURSE!! 
    // this is just a mark for Babel, where apply the imported style and ISOLATE it
    <style jsx> { style } </style>

  </div>
}
  1. babel sees the <style jsx> tag in some jsx code fragment, and understands that the imported style needs to be applied there.

  2. webpack scans selectors that are specified in the css file, and adds namespaces to them

  3. babel finds all classes added in jsx markup and adds a namespace class to them

  4. style isolation is achieved https://github.com/zeit/styled-jsx

  5. only now styled-jsx cannot normally scan dynamic classes such as ".active" and if we add a STANDARD for describing dynamic classes, styled-jsx will be able to scan them and apply namespace to them

  6. webpack and babel compile it to this:

comoinent.jsx:

import style from 'style.css'
import {useState} from 'react'

export default function Component() {
  const [active, setActive] = useState(false)
  function toggleActive(){ setActive(active => !active) }

  return <div .NAMESPACE .box .active={active} onClick={toggleActive}>

  </div>
}

style.css:

*.NAMESPACE { font-size: 50px }
.NAMESPACE.box { color: red }
.NAMESPACE.box.active { color: green }

and this compile to:

import style from 'style.css'
import {useState} from 'react'

export default function Component() {
  const [active, setActive] = useState(false)
  function toggleActive(){ setActive(active => !active) }

  return <div className={ `NAMESPACE box${ active ? " active" : "" }` } onClick={toggleActive}>
  </div>
}

feel the difference?

this:

<div .box .active={active} onClick={toggleActive}></div>

compile to this:

<div className={ `NAMESPACE box${ active ? " active" : "" }` } onClick={toggleActive}></div>

Conclusion

I see here 99% of those reading this issue have never written plugins for Babel, have never dealt with css isolation and have never written their own syntax language. they are still young, learn). the main thing for us, professionals, is to develop the jsx language, and improve it, wait until react developers join this discussion. serious people have gathered here and are solving serious issues, I’ll ask beginners to refrain from cluttering the discussion about adopting a new standard ^ __ ^ although you can watch and lern. We, the React developer community, are always happy to set a good example for the community. =)

@phryneas
Copy link

phryneas commented Apr 25, 2020

Are you a troll or really just extremly rude and patronizing?

Calling others "uneducated low-professional programmer" does not make you appear in a better light, it is just simply inacceptable. Please reconsider your behaviour.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@phryneas I just write a translator through Google, apparently he translates everything verbatim, in my rhetoric I didn’t mean anything rude =) i fix this words. Sorry, I'm from Russia and I am writing Russian expressions that are friendly, the Google translator apparently translates them literally and rudely. p.s. I didn’t want to offend anyone, if there are still rude expressions let me know, I will delete them. ^__^

let's get back to discussing the topic

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@mAAdhaTTah

New syntax that's only useful in one case doesn't really clear the bar for adding it.

Now the dot symbol is not used at all, what’s wrong with adding it as sugar for the className property for the web platform? no, literally. give a hypothetical situation when this could harm the jsx language? give an example of a situation where we would be sad that we had standardized such syntax, and then regretted it?

We must live in the real world, not in the world of hypotheses and false abstract idealistic fears. For this, please provide a hypothetical example of the harm from such a syntax?

Theory:

We decided to add attributes starting with a dot symbol, for the function which is present in each platform. but the dot character is already occupied by sugar for className. And we are sad.

OK Reality:

  1. What is this function that is present in every platform for which we want to use attributes starting from a point? give a specific hygpothetical example?

  2. Why can't we use a character other than a dot for this function?

I would like to hear your answers =) so that you prove the validity of your hypothesis.

@phryneas
Copy link

phryneas commented Apr 26, 2020

@MaxmaxmaximusGitHub you wrote:

You just did not understand my PHENOMINAL idea, like everyone who put dislike. Their programming level is fundamentally low compared to mine.

This has nothing to do with translation. Putting your ideas above others is foolish at best, but actually judging and devaluing the "programming skills" of others because they do not agree to you is rude in every spoken language on this planet.

Liking your own posts does not make it better.

But let's get back to discussing this topic:

Essentially your suggestion is to use css modules (which are already a thing) in an implicit combination between the named import style with a new jsx syntax in the same file.

So instead of writing

<li classname={clsx(style.user, { [style.banned]: user.isBanned, [style.admin]: user.isAdmin })}>

one would write

<li .user .banned={ user.isBanned } .admin={ user.isAdmin }>

This is arguably a bit shorter, but imposes quite a lot of problems:

  • it introduces an implicit relation between the component and the import style that is not visible to the developer on first sight
  • it will break in code where the variable style is already defined
  • this new syntax could be transpiled away with babel, but it would break all existing tooling:
    eslint, prettier, typescript and a lot other developer-level tools would all read this code before it ever passed babel, so they would need to be modified to work with it
  • this new syntax would now be supported by a lot of tooling, but at the same moment be incompatible with other frameworks that also use jsx, but not the classname property. React is not the only framework that uses jsx and as far as I know, classname is not standardized anywhere.
  • this would somehow "officially embrace" css modules over other valid approaches like
    • global css file
    • css frameworks like tailwind css
    • css-in-js frameworks like styled-components, emotion, or other jss-based solutions like material-ui's useStyle hook
      Each of these solutions can, depending on the situation, be far better or worse than each other of these solutions. Embracing one over all others would be a disservice to the community.

And all that to save a few characters - which is something that, if you are using a good component library and writing clean code should not be much of a concern anyways.

So you see, there are quite some good reasons people might be sceptical about your suggestion, and someone not agreeing to you might not be an indication of them being stupid, but maybe just an indication that they have other concerns than you.

Personally, I would definitely not call your idea "phenomenal". But that's just my opinion.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@phryneas

Essentially your suggestion is to use css modules

No, if you use css modules use them as before. Their syntax is extremely ugly, which made me develop a new syntax.

Those who don't like the ugly syntax of css modules can use my new syntax, getting concise, concise, and ISOLATED styles.

Moreover, both approaches can be used simultaneously. In other words, you can use as css modules using className, and at the same time use the new beautiful syntax.

I developed my ingenious syntax without breaking backward compatibility in order to maintain backward compatibility. After all, I am a professional programmer who first of all takes into account the needs of the ecosystem.

Example:

this:

function classes(classesArr){ classesArr.join(' ') }

<div .box .active={active} className={ cssModule.class + classes(['one', 'two']) }>

compile to:

function classes(classesArr){ classesArr.join(' ') }

return <div className={[cssModule.class + classes(['one', 'two']), `box${active ? ' active' : '' }`].join(' ') }>

You just did not understand the idea =) you should study this material https://en.wikipedia.org/wiki/Syntactic_sugar

So you see, there are quite some good reasons people might be sceptical about your suggestion

there is not a single reason, and all the reasons that you called are wrong, and they are based on your understanding of my idea, I hope I was better able to convey my idea to you, and refuted all your misconceptions.

@phryneas
Copy link

there is not a single reason, and all the reasons that you called are wrong, and they are based only on your misconceptions

So neither eslint, nor prettier or typescript will have to adopt their parser and release a new version to support this? Babel must be magic, even if it isn't being used.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

phryneas

[/sarcasm] typescript and babel NEVER do new syntax development, a babel doesn’t consist of thousands of plugins at all, and it is not just the kernel itself, why would they do it if Facebook supports and standardizes it? [/sarcasm]

I remind you that you still did not mention a single minus in my idea, and did not refute the pluses. the scales are still on my side.

This is arguably a bit shorter

I remind you that JSX itself is a SUGAR on top of javascript ;) but you did not resent

@phryneas
Copy link

I remind you that you still did not mention a single minus in my idea

I mentioned many, you answered condescendingly on one and ignored the rest.

I'm out.

"RFC" stands for "Request For Comment".
Obviously you are ignoring the biggest part of the comments while still somehow managing to be rude, so there's no point in trying to have any meaningful discussion here.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@phryneas

it introduces an implicit relation between the component and the import style that is not visible to the developer on first sight

does not introduce

it will break in code where the variable style is already defined

not break

this new syntax could be transpiled away with babel, but it would break all existing tooling:
eslint, prettier, typescript and a lot other developer-level tools would all read this code before it ever passed babel, so they would need to be modified to work with it

for the same reason, you can not add new syntax to all languages at all. the argument is not consistent and absurd.

his new syntax would now be supported by a lot of tooling, but at the same moment be incompatible with other frameworks that also use jsx, but not the classname property. React is not the only framework that uses jsx and as far as I know, classname is not standardized anywhere.

if your platform does not support the className property then why are you using it? (even as sugar "dot classes")

this would somehow "officially embrace" css modules over other valid approaches like

"dot classes" is just sugar on top of className, they are not connected in any way with any platform or framework and do not do anything you could not write with your hands. I already recommended that you familiarize yourself with the concept of syntactic sugar, I highly recommend reading the link from Wikipedia.

You see, absolutely none of your arguments has any weight. and the first two arguments generally comment on my RELATED SUGGESTION on the implementation of isolated styles with babel. which has an indirect relation to the main idea.

I wrote a post about isolating styles through import style.css just to let the person above explain that the new syntax does not break isolation of styles, but IMPROVES IT, because now it’s easier to write plugins for isolating styles.

and you NOT CAREFULLY read, and thought that the babel plugin is part of my main idea. although it was only offtop.

You see, I’m talking. 100% of what you wrote is either not true, or based on not understanding my idea =)

@juzhiyuan
Copy link

just unsubscribed...

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@MaxmaxmaximusGitHub

here smart and serious people decide how to develop the jsx syntax, I think you should just unsubscribe. thank you for understanding.

@juzhiyuan
Copy link

juzhiyuan commented Apr 26, 2020

how about id selector and data attribute? according to this proposal, we may use the following syntax in the future?

<div #awsome-id data-a="xxx" />

HTML has too many attributes I think...

BTW, classnames would meet your needs in my opinion.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@juzhiyuan

how about id selector and data attribute? according to this proposal, we may use the following syntax in the future?

The idea is good, it is already implemented in my framework. But in the case of jsx this would not be appropriate. Indeed, there are no such problems with writing id="myID" as with writing classes. Therefore, there is no particular reason to introduce a new syntax, and to occupy a whole sign under it.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@juzhiyuan

so far I have heard only one valid argument against such syntax. the fact that className only makes sense for the web, and making special sugar for the web is not cool.

but I parried it like this: now attributes that start with a dot are generally prohibited, the dot is idle, and at least it will bring joy to the web platform =) and there is not a single hypothetical situation in which the introduction of such syntax would harm other platforms

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@phryneas @juzhiyuan

and by the way, no one forbids we to add dynamic class names, and still use your favorite css modules

<div .[css.button] .[css.active]={ user.active } ></div>

compile to:

<div className={ `${css.button}${ user.active ? " " + css.active : "" }` }></div>

by the way, we can also add dynamic attribute names

let prop = "ololo"
<div [prop]="value"></div>

compile to

let prop = "ololo"
React.createElement('div', { [prop]: "value"} )

image

@MaxmaxmaximusGitHub MaxmaxmaximusGitHub changed the title Feature request: Add new syntax for jsx classes [Feature request]: Add new syntax for jsx classes Apr 26, 2020
@dancerphil
Copy link

We are use jsx based on Components more than html.

And I think className is only a tiny bit one of all of the props. Not to mention that it can only be used in html-based components.

It is not compatible with the overall idea of react, and more likely another anti-pattern.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

@dancerphil

We are use jsx based on Components more than html.

then why do you need to use className and aliases like dot classes which we are discussing here? I don’t catch the logical chain how does your platform relate to the web and to my issue about className aliases?

It is not compatible with the overall idea of react

The general idea of a react in declarative reactivity is when the function is executed again and does not have side effects. what does the syntactic sugar for the className property have to do with it, and what idea does syntactic sugar for the className property contradict?

and more likely another anti-pattern.

What does this pattern contradict?

while you speak only slogans, show concrete code examples or proofs of harm of such syntax?

You just said: React platform agnostic.

I fend off: Why from this we should not add syntactic sugar for popular use in one of the platforms?

what yours answer?

@dancerphil
Copy link

Why from this we should not add syntactic sugar for popular use in one of the platforms?

We should add it if and only if html adds it as a syntactic sugar. Otherwise we shouldn't.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

We should add it if and only if html adds it as a syntactic sugar. Otherwise we shouldn't.

  1. Why?
  2. jsx is subclass of xml, not subclass html. jsx and html is siblings, not parent and child.
  3. if html adds attributes starting with a dot, they can be described using dynamic jsx props <div [".prop"]="val"></div>
  4. html does not have the onClick and className attributes, html also has no expressions prop={1+2}, but jsx added it, but you say that jsx should not have added it

so...

therefore all your arguments are incorrect.

@MaxmaxmaximusGitHub
Copy link
Author

so, here’s something else I’ve come up with, I hope you enjoy it more #161

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 26, 2020

just compare

export default function TabTitle({active, onClick, children}) {

  return <div className={ classes('tab-title', {
    __active: active
  }) } onClick={ onClick }>

    { children }

    <style jsx>{ style }</style>
  </div>
}

and my version:

export default function TabTitle({active, onClick, children}) {

  return <div .tab-title .__active={active} onClick={ onClick }>

    { children }

    <style jsx>{ style }</style>
  </div>
}

is a syntax hell :

image

@gaearon
Copy link
Member

gaearon commented Apr 28, 2020

Hi @MaxmaxmaximusGitHub,

Thank you for your suggestion.
Generally saying, RFCs should be sent as pull requests, but I’ll reply here.

The scope of this RFC is much larger than just React. At the very least, it involves changes to JSX which we discuss here. As @phryneas noted, any changes to JSX are a lot of work for everyone involved to push through because they need to be coordinated with many partner projects, including Babel, ESLint, Prettier, TypeScript, Flow, IDEs and syntax highlighters, and so on. This doesn’t mean it’s insurmountable, but just that the bar for adding something to the JSX grammar is very high.

Any additions to JSX should be in harmony with other things we’ve been considering changing there. For example, syntax like .block .chat .active={state.active} presumably matches how currently foo bar become foo={true} bar={true}. However, with facebook/jsx#23 that would not be the case, where they would become foo={foo} bar={bar}. It is not clear whether that change is actually worth it, but it is far more commonly requested so this contradiction is something to consider. We can set that aside though.

I think this proposal can be divided in two parts:

  1. A way to conditionally apply classes without wrangling strings
  2. A first-class "CSS class" marker to be recognized by tooling

If (1) is your primary problem, you can already do this today with helpers like classnames:

import c from 'classnames';

<ul className="users">
  {this.props.users.map(user =>
    <li className={c('user', { banned: user.isBanned, admin: user.isAdmin )}>
      User {user.name}
    </li>
  )}
</ul>

<div className={c('box', { active })} onClick={toggleActive} />

<div className={c(css.button, user.active && css.active)} />

This seems equivalent in terms of how expressive it is.

If it's typing className that gets you, you could have a helper that you can spread:

<div {...c('box', { active })}>

This is somewhat cryptic, but your proposal also favors brevity.

But maybe this isn’t enough and you’re really after (2) — a first-class marker for CSS class names in syntax. I think there are a few problems with this.

Even in your example, I found <div .user> and <div user> somewhat difficult to tell apart. Sure, this can be "fixed" by different syntax highlighting, but this is so easy to miss in text that I don’t think it’s desirable. In the examples you’ve provided it’s less of a problem because those are clearly not <div> attributes, but it gets easier to get confused when you’re dealing with custom components:

<User .user user={user} .active />

Another problem with this is the clash resolution.

const props = {
  className: 'foo'
}
<div .bar {...props} />

We can't know statically what props contains, so we need to figure out what to do here. Do we ignore spread className in props and force you to pull it out statically? Do we forbid .bar syntax together with spread? Do we join classes?

Your proposal seems to prefer joining classes. We actually did that as a part of transferProps() helper in very early versions of React (around React 0.12 era). This has created a bunch of problems because in practice people often wanted to override them instead, and the joining was annoying. Spread syntax adds a layer of complexity to that already thorny situation.

If we pass <Foo .a={true} .b={false} />, and then it renders <Bar {...props} .a={false} />, how would joining work here? Intuitively, you would expect .a to override parent .a, but compilation to strings wouldn't let us do that. So now we have something that looks like props but doesn't behave like props at all. That seems like a flaw.

Another issue is just the indirection itself. This syntax gives preferential treatment to class names — but it's not clear that in the long term, those would be the preferred way to specify styles anyway. One might want tools to be able to minify them, or isolate them. As you said, it's possible to do this with a build step, but once you have a build step, why write classes at all? For example, Vue prefers the single file component style when you have a build step. If we colocate styles in the same file, it's not clear that we'd even need string names for classes. And then the proposed compilation output doesn't make much sense anymore. Then we might as well start over with something like #159, which already has some comments for future direction.

For the above reasons, I don't think this proposal is viable, so I'm closing it.


Your proposal aside, I need to also say a few things about the tone in your comments. Regardless of translation difficulties, statements like "their programming level is too low compared to mine" and "they are still young" are, as @phryneas already noted, very patronizing. While you may disagree with someone else's reasoning, belittling their knowledge or experience level does nothing to advance your arguments, and creates a hostile atmosphere that is not acceptable in this repository.

Moreover, "serious people have gathered here and are solving serious issues, I’ll ask beginners to refrain from cluttering the discussion about adopting a new standard" is the exact opposite of the goals of the RFC process — comments from beginners are as appreciated as comments from experts because they help us see issues from a new learner's perspective. That said, again, assuming that everyone who dislikes your idea is a beginner, is also patronizing in its own way.

I hope you can take this feedback to the heart and do better in future discussions.
Cheers, and thank you.

@gaearon gaearon closed this as completed Apr 28, 2020
@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented Apr 28, 2020

@gaearon

The scope of this RFC is much larger than just React. At the very least, it involves changes to JSX which we discuss here. As @phryneas noted, any changes to JSX are a lot of work for everyone involved to push through because they need to be coordinated with many partner projects, including Babel, ESLint, Prettier, TypeScript, Flow, IDEs and syntax highlighters, and so on. This doesn’t mean it’s insurmountable, but just that the bar for adding something to the JSX grammar is very high.

I retorted this argument above, I said that: AS THE PLAN FOR THE INTRODUCTION OF THE NEW SYNTHAXIS IS HIGH, I DONT WRITE PLUGINS MYSELF, BUT CALL THE COMMUNITY TO STANDARDIZE this syntax. I said that EXACTLY because the inertia of the community is high, I need Facebook support. hence your argument is incorrect.

p.s. I read further I answer further, I will edit

Any additions to JSX should be in harmony with other things we’ve been considering changing there. For example, syntax like .block .chat .active={state.active} presumably matches how currently foo bar become foo={true} bar={true}. However, with facebook/jsx#23 that would not be the case, where they would become foo={foo} bar={bar}. It is not clear whether that change is actually worth it, but it is far more commonly requested so this contradiction is something to consider. We can set that aside though.

I suggested above that “literal” attribute names can be expressed as expressions. I countered this argument. html allows any non-whitespace names as attribute names, jsx has problems with this, and I solved them in this rfc #161.

<div [".46&(ghkFGHKDFGFHK8&*...fddf.df..dfdfdfd"]="11"><div>
<div [".ololo"]="trololo"><div>

your mistake in understanding my idea was that you thought it was the literal name of the property, not the syntax construction.

is not attr ".fddfdfdf", its SYNTAX CONSTRUCTION, jsx compile time. you simply did not understand this, and mistakenly assumed that this is the name of the property, although this is just the syntax that turns into className

so this contradiction is something to consider

there is no contradiction, their syntax does not contradict what I proposed, no ambiguity, you just made a mistake

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented May 1, 2020

plus this syntax allows you to finally do this:

function List(props) {
  return <ul .list {...props}></ul>
}


function Component() {
  return <List .red></List>
}

its render to:

<ul className="lisit red></ul>

ability at the precompilation stage to competently concatenate dot classes and className is phenomenal =)

Adequate guys, do you want me to write a transpiler plugin for this syntax, and add support for it in your IDEs? Who agrees? I think to create a separate repository =)

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented May 4, 2020

@gaearon

If we pass <Foo .a={true} .b={false} />, and then it renders <Bar {...props} .a={false} />, how would joining work here? Intuitively, you would expect .a to override parent .a, but compilation to strings wouldn't let us do that. So now we have something that looks like props but doesn't behave like props at all. That seems like a flaw.

There is no contradiction; the semantics of concatenation do not imply a cascading class system. the concatenation system assumes only syntactic sugar on top of the existing semantics.:

function Button (props) {
  return <button .lol={true} {...props}></button>
}

<Button .lol={false}></Button>

compile to:

function Button (props) {
  return <button {... {
    ...props,
   { (className: true ? "lol" : "") + (props.className ? props.className : "")  }
  }}}></button>
}

<Button .lol={false}></Button>

I probably need to repeat the word syntactic sugar 20 times, because people don’t hear it from 1 to 5 times. we should not add new semantics:

var classOne = true ? "ololo" : ""
var classTwo = false ? "ololo" : ""

className = [classOne, classTwo].join(" ")

dot class expressions are just SUGAR on top of this code:

 .class={exp}

is

exp ? "class" : ""

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented May 4, 2020

@gaearon

that way we could create a special runtime string tag for concise compiled code, something like React.classList

<button .button .__active={user.active}></button>

compile to:

React.classList = function () {
  // magic
}

function Button(props) {
  return <button
    className={ React.classList`button __active${ user.active }` }>
  </button>
}

and if we find the declarative property of className, then classList will be called :

<button .button .__active={user.active} className="AZAZA"></button>

compile to:

React.classList = function () {
  // magic
}

function Button(props) {
  return <button
    className={ React.classList`button __active${ user.active } ${"AZAZA"}` }>
  </button>
}

classList will competently combine classes, and return a string. without breaking backward compatibility.

in other words, if we have at least one dot classes then react adds a React.classList stringTag

<div .box .chat></div>
<div className={ React.classList`box chat` }></div>


<div .box={true} .chat></div>
<div className={ React.classList`box${true} chat` }></div>


<div .box={true} .chat className={ classNameExp }></div>
<div className={ React.classList`box${true} chat ${classNameExp}` }></div>


<div .box={true} .chat className={ classNameExp } {...props}></div>
<div className={ React.classList`box${true} chat ${classNameExp}` } {...props}></div>

@gaearon catch the essence of my proposal? =) no new semantics, just sugar.

in other words, I can use it right now if I create a special stringTag =)

I just have to write a lot of code, and ide will not understand how to highlight it, because there is NO STANDARD, I'm trying to introduce this standard for describing classes

<button .lol={false}></button>

this is the same as:

<button></button>

false switches the class only at this level, in other words, we simply DO NOT ADD it. this is the current semantics of react. why does she surprise you i don't understand

Moreover, this string tag can be processed with babel, and generate an inline javaascript expression.

Do you understand now all the genius and simplicity of my idea?

TEMPORARRY SOLUTION:

function bool(strings, ...exps) {
  let result = ''

  for (let i = 0; i < strings.length; i++) {
    let string = strings[i]
    if (!string) continue
    let exp = Boolean(exps[i])
    result += string + exp
  }
  
  return result
}


<div className={ bool`chat __active-${ false } __admin-${ null }` }></div>

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented May 4, 2020

cheers guys, I came up with a workaround !!!! thank god these guys did not forbid the dollar symbol !!! =)

<div $chat $active={ false } $opened={ null }></div>

I will write a babel plugin that implements the behavior that I described here. the dollar sign will temporarily act as a dot symbol =) this way we can test new syntax

yes, it breaks backward compatibility, since now you can’t use properties starting with the dollar sign, but it's not my fault, but the guys from React =)

but living even that way will be much easier. of course it's a fierce crutch, but at least something

p.s. yes and goodbye typescript

I experimented a lot, and technically, the length of such a syntax is literally equal to the length of the syntax I proposed:

image

but visually the dollar symbool is annoying and far less logical than the dot symbool.
to implement override and disable classes semantics, I will write the runtime mergeProps function, but this will be the next step. if className is passed to the component, it will be passed by the data object; if className is passed to the element, it will be passed as a string.

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented May 4, 2020

@gaearon generally we can make a tricky className object like

{'class1': true, 'class2': false, toString(){ /* magic */ }}

and use the runtime function merge on rest props or className prop

Example:

function Chat (props) { 
  return <div .chat .active={ true } className="LOL" {...props}></div>
}

<Chat .active={ false }>

compile to:

function Chat (props) { 
  return <div { ...merge({className={chat:true, active:true}}, {className:"LOL"}, props)} }>

  </div>
}

<Chat className={ active: false }>

yes i'm just a genius

image

UPD: its works:

image

render:

image

Solution:

  1. then create two new functions
  2. React.classList() create class list object with toString method
  3. React.merge competently combines rest properties taking into account classList and not only
  4. jsx will sometimes compile using these functions
  5. so we add new classList semantics to the kernel, and then add syntactic sugar for it, what do you think @gaearon ?

Approximate implementation:

React.merge = function (...propsList) {
  const props = Object.assign({},...propsList)

  if (props.hasOwnProperty('className')) {
    const classNames = propsList.map(p => p.className)
    props.className = Object.assign({}, ...classNames)
  }

  return props
}


React.classList = function (classNames) {
  return Object.assign({}, classNames, {toString:function(){ /*magic*/ }})
}

@MaxmaxmaximusGitHub
Copy link
Author

MaxmaxmaximusGitHub commented May 4, 2020

@gaearon React.merge should also be able recursively to combine other React objects.

let ref1 = React.createRef()
let ref2 = React.createRef()

let refCompined = React.merge(ref1, ref2) // return functional ref for example

or classLists

let classes1 = React.classList({})
let classes2 = React.classList({})

let classesCompined = React.merge(classes1 , classes2 )

and of course just props or any other ordinary object

or classLists

let ref1 = React.createRef()
let classes1 = React.classList({})

let ref2 = React.createRef()
let classes2 = React.classList({})

let props1 = {cCclassName: classes1, rrReeffff: ref1}
let props2 = {cCclassName: classes1, rrReeffff: ref1}

let propsCompined = React.merge(props1, props2)

where React encounters a familiar type that can combine, it applies a clever merge, where react meets an unfamiliar type, it simply uses Object.assign

and on top of all this we will add a new syntax:

  1. dot classes is a classList
<div .chat .active={false}></div> // new syntax

<div className={ React.classList({chat: true, active: false}) }></div>
  1. merge is a React.merge
<div ...props><div> // new syntax

<div { ...React.merge(props) }><div>

merge syntax is like rest syntax, but uses smart merge, merge statement is written without curly braces. in this, it differs from the rest =) operator, while retaining a similar syntax, while maintaining a cognitive map in the programmer’s head.

as for React Native programmers, since they don't need classList, they simply won’t use it =), but if they want, they can :)

P.S.

with the merge operator, for example, it’s very convenient to combine several links into one:

function Chat() {

  const ref1 = useSize()
  const ref2 = useAnimation()

  return <div ...{ref:ref1} ...{ref:ref2}>
  
  </div>
}

or merge class names

function Chat() {

  const classOne = React.classList({ololo: true})
  const classTwo = React.classList({trololo: true})
  
  return <div ...{className:classOne} ...{className:classTwo}>

  </div>
}

for example, the merge operator can combine functions into one =), which is convenient for event handlers, for example:

function Chat() {

  return <div ...{onClick:handler1} ...{onClick:handler2}>

  </div>

}

the merge operator syntax still needs to be considered) I’ll do it the other day. I will try to experiment with this, using the dollar sign as a replacement for the point, since it is already allowed.

...exp is a $$$exp, .class is a $class

cool i came up with huh @gaearon ? and you laughed ^ __ ^

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

9 participants