facebook / react

The library for web and native user interfaces.

Home Page:https://react.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

React Fire: Modernizing React DOM

gaearon opened this issue · comments

commented

For latest status, see an update from June 5th, 2019: #13525 (comment)


This year, the React team has mostly been focused on fundamental improvements to React.

As this work is getting closer to completion, we're starting to think of what the next major releases of React DOM should look like. There are quite a few known problems, and some of them are hard or impossible to fix without bigger internal changes.

We want to undo past mistakes that caused countless follow-up fixes and created much technical debt. We also want to remove some of the abstraction in the event system which has been virtually untouched since the first days of React, and is a source of much complexity and bundle size.

We're calling this effort "React Fire".

🔥 React Fire

React Fire is an effort to modernize React DOM. Our goal is to make React better aligned with how the DOM works, revisit some controversial past decisions that led to problems, and make React smaller and faster.

We want to ship this set of changes in a future React major release because some of them will unfortunately be breaking. Nevertheless, we think they're worth it. And we have more than 50 thousands components at Facebook to keep us honest about our migration strategy. We can't afford to rewrite product code except a few targeted fixes or automated codemods.

Strategy

There are a few different things that make up our current plan. We might add or remove something but here's the thinking so far:

  • Stop reflecting input values in the value attribute (#11896). This was originally added in React 15.2.0 via #6406. It was very commonly requested because people's conceptual model of the DOM is that the value they see in the DOM inspector should match the value JSX attribute. But that's not how the DOM works. When you type into a field, the browser doesn't update the value attribute. React shouldn't do it either. It turned out that this change, while probably helpful for some code relying on CSS selectors, caused a cascade of bugs — some of them still unfixed to this day. Some of the fallout from this change includes: #7179, #8395, #7328, #7233, #11881, #7253, #9584, #9806, #9714, #11534, #11746, #12925. At this point it's clearly not worth it to keep fighting the browser, and we should revert it. The positive part of this journey is that thanks to tireless work from our DOM contributors (@nhunzaker, @aweary, @jquense, and @philipp-spiess) we now have detailed DOM test fixtures that will help us avoid regressions.

  • Attach events at the React root rather than the document (#2043). Attaching event handlers to the document becomes an issue when embedding React apps into larger systems. The Atom editor was one of the first cases that bumped into this. Any big website also eventually develops very complex edge cases related to stopPropagation interacting with non-React code or across React roots (#8693, #8117, #12518). We will also want to attach events eagerly to every root so that we can do less runtime checks during updates.

  • Migrate from onChange to onInput and don’t polyfill it for uncontrolled components (#9657). See the linked issue for a detailed plan. It has been confusing that React uses a different event name for what's known as input event in the DOM. While we generally avoid making big changes like this without significant benefit, in this case we also want to change the behavior to remove some complexity that's only necessary for edge cases like mutating controlled inputs. So it makes sense to do these two changes together, and use that as an opportunity to make onInput and onChange work exactly how the DOM events do for uncontrolled components.

  • Drastically simplify the event system (#4751). The current event system has barely changed since its initial implementation in 2013. It is reused across React DOM and React Native, so it is unnecessarily abstract. Many of the polyfills it provides are unnecessary for modern browsers, and some of them create more issues than they solve. It also accounts for a significant portion of the React DOM bundle size. We don't have a very specific plan here, but we will probably fork the event system completely, and then see how minimal we can make it if we stick closer to what the DOM gives us. It's plausible that we'll get rid of synthetic events altogether. We should stop bubbling events like media events which don’t bubble in the DOM and don’t have a good reason to bubble. We want to retain some React-specific capabilities like bubbling through portals, but we will attempt to do this via simpler means (e.g. re-dispatching the event). Passive events will likely be a part of this.

  • classNameclass (#4331, see also #13525 (comment) below). This has been proposed countless times. We're already allowing passing class down to the DOM node in React 16. The confusion this is creating is not worth the syntax limitations it's trying to protect against. We wouldn't do this change by itself, but combined with everything else above it makes sense. Note we can’t just allow both without warnings because this makes it very difficult for a component ecosystem to handle. Each component would need to learn to handle both correctly, and there is a risk of them conflicting. Since many components process className (for example by appending to it), it’s too error-prone.

Tradeoffs

  • We can't make some of these changes if we aim to keep exposing the current private React event system APIs for projects like React Native Web. However, React Native Web will need a different strategy regardless because React Fabric will likely move more of the responder system to the native side.

  • We may need to drop compatibility with some older browsers, and/or require more standalone polyfills for them. We still care about supporting IE11 but it's possible that we will not attempt to smooth over some of the existing browser differences — which is the stance taken by many modern UI libraries.

Rollout Plan

At this stage, the project is very exploratory. We don't know for sure if all of the above things will pan out. Because the changes are significant, we will need to dogfood them at Facebook, and try them out in a gradual fashion. This means we'll introduce a feature flag, fork some of the code, and keep it enabled at Facebook for a small group of people. The open source 16.x releases will keep the old behavior, but on master you will be able to run it with the feature flag on.

I plan to work on the project myself for the most part, but I would very much appreciate more discussion and contributions from @nhunzaker, @aweary, @jquense, and @philipp-spiess who have been stellar collaborators and have largely steered React DOM while we were working on Fiber. If there's some area you're particularly interested in, please let me know and we'll work it out.

There are likely things that I missed in this plan. I'm very open to feedback, and I hope this writeup is helpful.

I love this. Reducing bundle size and the "class" prop are changes that will be very welcome.

Great work!

Attention form library authors! 🤣

className → class is fantastic

What about all the others? Seems weird to still be doing clipPath, htmlFor, tabIndex, etc.

commented

Adopting class is a major breakthrough in making the library more friendly for beginners. Congratulations.

This is awesome. I'm so curious how the move to class is actually going work with props.

Seems like ({ class }) => <div class={class} /> would initially present a reserved keyword problem?

This is fantastic news, thanks @gaearon!

I love every of these points, except the className change. It seems downright contradictory to the goal the other points are pursuing (aligning with the DOM API). React binds to DOM properties, not HTML attributes (this is this even articulated in the first point). The DOM Element property is named className, not class. So why would it be named class in React?

Fantastic! Do you have a goal for bundle size reduction?

commented

What about all the others? Seems weird to still be doing clipPath, htmlFor, tabIndex, etc.

I’m open to discussion but I’d argue these changes aren’t worth it (except for maybe).

I think a re-write of the event system is the most interesting aspect of this. There is significant opportunity to reduce the bundle size and ease community contributions.

Let's do it!

I wonder if it would be worthwhile to also work on releasing JSX 2.0 at the same time? People are going to need to re-learn a handful of things anyway. Maybe it's better to have to re-learn a bunch at one time rather than a few things twice over a period of time? Just a thought that occurred as I read this.

commented

I love every of these points, except the className change. It seems downright contradictory to the goal the other points are pursuing (aligning with the DOM API). React binds to DOM properties, not HTML attributes (this is this even articulated in the first point).

And yet if we pass an unknown key/value pair it will be treated as an attribute since React 16. So we’re already inconsistent. Also, my comment was about users being wrong in expecting React to set value attribute. Whether React API uses attribute name or property name in its API is compeletely orthogonal.

I’ve defended your side of this argument for years but I think now that this is friction that’s just not worth it. You don’t gain anything from it. Just letting people use class has no negative effects except it doesn’t work with destructuring, and the migration cost. Everybody complains about it when they learn React. I think doing what people expect in this case is more important than being pedantic. And since we’re changing other DOM things anyway, let’s batch this together.

As long as React Fire is blazing fast.... 👍

These changes are all fantastic. I'm way excited about this and its implications for react-testing-library. In particular, events being bound to the react root (or maybe even drop event delegation altogether as it may not be necessary in modern environments anymore?), potentially removing/rewriting synthetic events, and onChange -> onInput will seriously improve the implementation of react-testing-library and the experience in using the tool.

I'd love to provide feedback on this as it's being implemented.

commented

maybe even drop event delegation altogether as it may not be necessary in modern environments anymore

We considered this but think this might be an oversimplification. Event delegation lets us avoid setting up a bunch of listeners for every node on the initial render. And swapping them on updates. Those aspects shouldn’t be ignored. There is likely more benchmarking to be done there though.

commented

@tannerlinsley ({ class: className }) => <div class={className} /> unfortunately this will kill jsx 2.0 object short hand notation (<div class />), but so be it ...

It would be super, super nice if class could finally take objects and arrays btw next to strings.

commented

Do you have a goal for bundle size reduction?

Dropping a third of React DOM would be nice. We’ll see. It’s hard to say early but we’ll do our best.

Wow, this is an enumeration of almost all the design decisions I mention when people ask me about React cons.

Love the direction this is going.

What would the upgrade path be for libraries that use className and want to support multiple versions of React?

@gaearon Maybe it's not a big deal, today its nice to say "props are DOM properties not HTML attributes", now it'll be "except className, that one is the HTML name". I'd also like to be able to copy/paste SVG w/o 10 minutes of messing around with attributes to match up with React's

What about htmlFor?

It feels like the className -> class transition will have to be done very carefully, probably over an extended period of time. It's going to cause a lot of issues for the ecosystem, as virtually every component will need to be changed - even very trivial ones. This is mostly fine if you "own" the code and there's a codemod, but when you're dependent on 3rd party libraries you're largely at the mercy of maintainers.

The rest of the changes seem relatively low risk from an ecosystem point of view, but getting rid of className will really cause a lot of pain. It seems like it should be possible to split that into a separate issue and release it on a different schedule to the rest of the 🔥 changes.

I agree with @felixfbecker
Everything sounds awesome and would improve quality for both developers and users, but the className change.

Beeing able to deconstruct all properties but one would certainly cause more confusion and be harder to explain to new users than that they need to use className because class is a keyword (which they can clearly see when the misstake is made, as it's colored differently). Working around class in deconstructing requires introducing new syntax or a completely different way to read out that specific property that would only work untill you need to use a rest property.
It creates many problems just to save four characters.

@felixfbecker it could it be class/for in JSX and className/htmlFor in the JS version?

<label class="foo" for="bar">..</label>

would be

React.createElement('label', {className: 'foo', htmlFor: 'bar'}, '..')

Great plan! Nice to here that! 👏👏👏

This is awesome, I can't wait to dig into the new stuff.

Drastically simplify the event system (#4751).

Now react can not delegate handler to custom elements, without extending ReactDOM definition. #9242

It means React can not set custom handler on custom element like <x-component onMyEvent={ev => {...}} />

@gaearon
Do you have any plan about this?

All these changes are excellent but the biggest, meta change of them all is the size reduction in my opinion. React has a mean DX, but it's fat enough that downloading and parsing it on average networks and in particular mobile is a problem.

We could have it all!

Would rewriting the event delegation open the door for fixing #1254; where some events degrade perf for the node they are attached to (which for React means the whole app).

Also, as a long shot, have you considered having synthetic dataSet props? Not being able to type allowable HTML props (because of data-*) leads to a ton of bugs when forwarding props to the DOM nodes. Currently typed React components have to choose between exact types for props and allowing data attributes.

I'm excited

@ryanflorence A bit offtop but it's kinda sad that no one (as far as I know of) had the idea to make a html/css/svg -> jsx transfomer in order to ease migrations to React with so many trivial changes to map HTML attrs to React props. So many man-hours wasted perfoming mostly find-and-replace :(

It's very strange to see the following in the same issue (and the comments):

Our goal is to make React better aligned with how the DOM works
className → class
Seems weird to still be doing clipPath, htmlFor, tabIndex, etc.

When all these are direct counterparts of DOM APIs

And this argument doesn't pass muster:

I’ve defended your side of this argument for years but I think now that this is friction that’s just not worth it. You don’t gain anything from it. Just letting people use class has no negative effects except it

React has always been just a very thin layer on top of JS. So everything else besides JSX's angle brackets was JS and had a direct counterpart in DOM APIs for things directly related to DOM: className, clipPath, htmlFor, tabIndex etc. etc. etc.

With the decision to introduce class React stops being a thin layer (class is a reserved word in JS) and breaks away from the declared goal of being more compatible with DOM APIs.

It's especially jarring to see that you want to both "Migrate from onChange to onInput" because it's inconsistent with the DOM and become inconsistent with the DOM by migrating className -> class.

Additionally, this opens a full can of worms, as people will demand (and are already demanding) changes to other parts. Just in the comments: why do we use dataset instead of data-*? Maybe because data-* is not valid JS (much like class) and because it's consistent with the DOM APIs? Why don't we change htmlFor? Because for is a reserved word and htmlFor is in DOM APIs? Etc. etc. etc.

And yet if we pass an unknown key/value pair it will be treated as an attribute since React 16. So we’re already inconsistent.

@gaearon which is also the reason why React is the only library scoring bad on the CustomElements Everywhere interop test: https://custom-elements-everywhere.com/
And there's many issues asking to change it: #11347, #7249, #9230

This may be a non-issue, but is the similarity of React Fire to React Fiber going to be confusing for non-native English speakers? I've often thought that Newark Penn Station and New York Penn Station being on the same train line here in NYC was a particularly cruel joke on tourists.

If it's a real concern, you could call it React Flame and still keep the fire 🔥 emoji.

commented

Would rewriting the event delegation open the door for fixing #1254; where some events degrade perf for the node they are attached to (which for React means the whole app).

Fixing #1254 in some form is definitely something I’d like to see.

Also, as a long shot, have you considered having synthetic dataSet props?

I don’t want to commit to something like this now because there’s a bigger surface. Richer interface for DOM (ariaSet, dataSet, classList) makes sense but it’s not clear how much we want to invest in that in React DOM, versus a higher level library that gives you a richer DOM API. Since these changes are more cosmetic but have high surface area, I’d hold off with them.

React Blazing 🔥

@ryanflorence A bit offtop but it's kinda sad that no one (as far as I know of) had the idea to make a html/css/svg -> jsx transfomer in order to ease migrations to React with so many trivial changes to map HTML attrs to React props. So many man-hours wasted perfoming mostly find-and-replace :(

@jxub
https://transform.now.sh/html-to-jsx/

So excited for the new changes, you humans from Facebook are making history with this migration. 50k components will be migrated over to React Fire ?
Your test suites must be so tight <3

commented

is the similarity of React Fire to React Fiber going to be confusing for non-native English speakers?

maybe for hard-of-hearing folks, too (or those with other conditions affecting word discrimination)

Thanks for sharing @gaearon, that’s amasing!

I’d love to see a JSX 2.0 release solving the white space and new lines issue that occurs when we compile our code with Babel and Typescript.

There are different issues opened and I tried to contribute but got told off cause the React team needs to review everything around JSX.

This issue relates to the dom anyway, cause the way we translate jsx to js does not allow to what the w3c says.

This is the issue facebook/jsx#19

My comment is at the very end.

I think, className is ok. Let it be what it is. Don't add insult to injury.

Can someone please clarify how this affects existing handlers?

Stop reflecting input values in the value attribute

Will React still have controlled inputs with accurate event.target.value updates in handlers, or does this only affect reading values from refs and DOM Nodes?

commented

@nickmccurdy it affects what you see in the browser devtools

@tomdale React Ember 🔥

Nice! I'm waiting to see full list of changes in React 17.
I believe it will be a new era of ReactJS. 🔥⚛️

@tomdale Hmm: Yarn, Fiber, Fabric; maybe another clothing related term could be used? 😆

commented

And yet if we pass an unknown key/value pair it will be treated as an attribute since React 16. So we’re already inconsistent.

@gaearon which is also the reason why React is the only library scoring bad on the CustomElements Everywhere interop test: https://custom-elements-everywhere.com/

No, that's not the reason (custom and normal elements are completely separate code paths). The reason is that we already had the old behavior and didn't want to break backwards compat unless the new behavior was solid. I think it would make sense to tackle better custom element interop as part of this umbrella.

is the similarity of React Fire to React Fiber going to be confusing for non-native English speakers?

Both are internal codenames and don’t really carry any significance once the projects are completed. React Fire is just an effort to make React DOM better — and by the time it becomes production-ready it will be just React DOM.

Will React still have controlled inputs with accurate event.target.value updates in handlers, or does this only affect reading values from refs and DOM Nodes?

Yes, because event.target.value is a property. We're talking about stopping updating attributes. Which no other popular libraries do (AFAIK) and which causes countless problems. It shouldn't affect your code unless you're relying on CSS selectors on the value (which is probably bad anyway).

Nice! I'm waiting to see full list of changes in React 17.

Note we're not committing to this being ready by 17. It might be 18. Or 19. 😅

It's nice to see steady development on such a good library like React. 🎉 class will make the usability much better, it's worth it imo

@gaearon

custom and normal elements are completely separate code paths

That itself seems like something to fix too. Is there any reason not to treat all elements the same? That's the intention of the HTML and DOM specs.

commented

@justinfagnani As discussed previously, the reason we didn't do it at the time was because there was no convention for how to tell whether to set a property or an attribute — and there was a risk that by using a check we risk making it impossible for web platform to add new properties to the prototype. I think by now there's already some sort of consensus in the RFCs and PRs that @robdodson has been working on, and we can probably pick it up from there.

👍 🔥 🔥 🔥

React Fire should also allow us to apply some of the clever performance optimizations that Inferno has – but haven't been able to apply because of breaking changes. Exciting times :)

Related to the proposed className -> class rename: I'd love a classes property that took an array of strings. That would save me the trouble of a lot of string manipulation (or the use of classnames) in my components.

I think the only disadvantage of a classes prop would be that arrays containing the same strings in the same order would cause a re-render in pure component, while a string of the same CSS classes wouldn't. Honestly though, it seems like a minor issue. I think most React devs already know the tradeoffs of arrays & objects in props.

@gaearon have plans for backward compatibility? Maybe follow the same path as React Fiber, adding warnings about the changes and giving time for large codebases update that without losing new updates.

commented

Regarding class and className.

I know we won't get a broad agreement on this whichever way we go. People have really strong opinions about this one. I want to share how I'm thinking about it, in a hope that it will be helpful.

Component API Should Feel Idiomatic

There is a common argument that React "matches JavaScript" and thus className is preferred. I think this assertion is subtly misunderstood so I'd like to focus on it a little.

In React, first and foremost, we care that using a React component should feel like idiomatic JavaScript. This means that if I use a hypothetical <Table> component, I expect its props to be camelCase:

<Table
  rowHeight={10}
  headerBorderInset={5}
  renderRow={this.renderRow}
/>

I don't expect to see prop names like row_height or row-height in a component's public API. Component's props are an object (kind of like an "option bag"), and we generally expect those options to be camelCase. This may not be always idiomatic in DOM, but the DOM is not very consistent in many places. React aligns with the JavaScript ecosystem which overwhelmingly uses camelCase.

But what about the DOM? This is where it gets thorny.

DOM Properties Are Not Just "Attributes in JS"

In DOM, we have attributes and properties. Attributes are the things you usually see in HTML. Properties are the things you usually set from JS. But crucially, DOM APIs exist both for setting properties and for setting attributes — and they're not even always setting the same thing.

node.value = 10; // setting a property
node.setAttribute('value', '10'); // setting an attribute

In many cases it doesn't matter. In some cases it does. But maybe not in the way one might think from using React (which has one abstraction over both).

React Is Not Just Setting Properties

A common misconception is that since React currently uses the camelCase convention for most DOM props, it means React is setting DOM properties. This is wrong.

In fact, React is currently using attributes for almost all props it supports. In some cases, like value, this is causing issues (which as I discussed we want to revert). In other cases, this is actually great — because we don't have to include a list of supported properties into the React bundle. Using attributes under the hood is what allowed a major size reduction in React 16.

My point here is that whether React uses properties or attributes internally is an implementation detail — and has nothing to do with whether React DOM element API should be using property names, attribute names, or even some other names that make sense.

Still, Let's Just Use Property Names?

Okay, properties and attributes are an implementation detail. But why not just standardize on using DOM property names since those were specifically made "for JavaScript"? Isn't that how React API is designed today?

Well, not quite. Only one of the props enumerated below corresponds to a real DOM object property:

<div
  tabIndex={1}
  data-id="123"
  aria-live="polite"
  nopin="nopin"
  itemType="http://schema.org/Movie"
  onClick={function() { alert('hi') }}
/>

Ironically, the only prop above that has an actual DOM property with the same name corresponding to it (tabIndex if you weren't sure) is actually being set by React as an attribute!

So by this point you probably see it's neither clear-cut nor consistent. In some cases properties don't exist (such as for custom, non-standard attributes), in some cases React could provide a richer API (data- vs dataSet) but currently doesn't.

In some cases React intentionally chooses to deviate (onClick in React vs onclick DOM property) because it makes more sense for custom React components. This is because React components often expose more complex event handlers like onItemClick. It would be very inconsistent if you wrote <Button onclick> but <Table onItemClick>. And <Table onitemclick> isn't camelCase, which we wanted to avoid in a component API.

Above, I explained that React already isn't consistent about "always using DOM property name", that React doesn't even actually use properties internally (so that rule of thumb doesn't describe the actual mechanics either), and that in many cases DOM properties simply don't exist so we have to stick with allowing the attribute name.

If Not Property Names, Let's Be Consistent and Use Attribute Names?

So why not go with using only attribute names? This could be plausible. But now we bump into the very first consideration we brought up. Using a React component should feel like idiomatic JavaScript. But often components forward at least some props to the underlying DOM element.

<Button
  borderColor='red'
  tabIndex={1}
 />

 // renders...

 <button
   tabIndex={1}
/>

It would be awkward for a custom Button to accept props with inconsistent capitalization:

<Button
  borderColor='red'
  tabindex={1}
 />

This forces the consumer to always remember if a certain prop is an actual DOM prop, or just a part of the component contract. Even that distinction is fuzzy — a component may choose to first pass a certain prop through, but then to actually start using it for some extra logic. Where do you put the boundary between "DOM props" and "other props"?

I think this is the primary reason it's desirable for props like tabIndex, cellSpacing, and most other DOM-related props to follow the camelCase convention. It's not because they're DOM property names. It's because they often end up in component APIs. And we want component APIs to be consistently camelCase.

We want to make it easy for custom components like Button to wrap and forward them without either "translating" them to the attribute name at the point where they flow into the DOM, and without introducing non-camelCase props into a custom component API.

This also explains why props like data-*, aria-*, and custom attributes are reasonable exceptions (even though we could make richer APIs for them). They are rarely passed to custom components. Typically, they are too coupled to the DOM to be useful in custom components — and instead, they become an implementation detail of something like a <Modal> or a <Button> with a richer camelCase API.

React Properties Already Don't Match DOM Properties

If the "DOM property name" convention didn't work out, we need something else. What is it? Could it be "camelCase version of the attribute name"? It seems like this almost always already checks out.

If this sounds too radical, consider that we're already doing this. We support something called srcSet. But the DOM property name for it is srcset. We have autoCapitalize but the DOM property is called autocapitalize. We have autoFocus but the DOM property is autofocus.

We are already deviating from DOM property names when they don't match the camelCase JavaScript convention. Which brings us to class.

Finally: className vs class

Part of the original justification for making it className was because React was setting DOM properties, and className is the name of the DOM property.

However, as I explained above, React doesn't use properties anymore except three or four special case. More importantly, React doesn't even consistently use DOM property names — rather, it uses names that would look natural when used from JavaScript, regardless of internal naming inconsistency in either DOM attribute and property names. And that is because React cares most about keeping prop names for custom components feel "JavaScripty". In this sense, tabindex is not "JavaScripty" — but both class and className are.

Another argument against class early on was that code like this wasn't valid ES3 (relevant for IE8):

// Not valid in ES3
// Valid in ES5
var props = { class: 'foo' };

But most don't write ES3 anymore. Either you're using a toolchain like Babel or you're likely targeting IE9+ — React doesn't even support IE8 now.

So the only inconvenience left with class is that you can't write this:

// Not valid at all :-(
const class = props.class;
const { class } = props;

But I think that with time, this argument is not strong enough by itself. React doesn't force you to use destructuring or using this specific variable name, and writing something like

// Valid
const {class: cls} = foo;
const cls = props.class;

isn't that much more effort.

Typically people pass class down far more often than read it because many components contain more than one inner <div> or other host elements. So you end up writing <div className> much more often than wanting to destructure class. And thus the change to class would save more keyboard typing than it would introduce.

There is another important point here.

Passing class down through many levels is not a great pattern by itself. It's necessary for libraries, but in application code it often leads to fragile components. The ones whose styles break all the time because there's a hundred different callsites each appending a different class, causing cascade bugs. So it's not clear how valuable it is to encourage destructuring class in the first place. I think it's fine that you need to write one more line of code to read it from props (or you can just use props.class and not think about it).

If you are writing a component that is very close to the DOM (and thus it makes sense for it to take class as a prop), you likely want to forward other props too. So you can use rest syntax in destructuring:

// Valid in ES2018

function Button({ color, ...rest }) {
  const buttonClass = rest.class +  ' Button-' + color;
  return <button {...rest} class={buttonClass} />
}

And if you didn't need to modify it then you could've just forwarded {...rest} without even reading class from it. So the destructuring limitation might help encourage better component design.

Why Not Both?

why not both

Finally, can't we just support both class and className? In a way, we already do, but React yells at you for it with a warning. There is a reason for this.

If we support both without warnings, then the community will split over which one to use. Each component on npm that accepts a class prop will have to remember to forward both. If even one component in the middle doesn't play along and implements only one prop, the class gets lost — or you risk ending up with class and className at the bottom "disagreeing" with each other, with no way for React to resolve that conflict. So we think that would be worse than status quo, and want to avoid this.

Summary

If React was open sourced today, it seems like the pros of allowing class (closer conceptually to what most people expect, less typing for the most commonly used prop) outweigh the downsides (slightly more typing to intercept it — in which cases you'll probably just want the spread operator anyway).

What we used to see as downsides (inconsistent with DOM properties) is moot because we neither set DOM properties anymore, nor even strive to be consistent with them. Instead we're aiming to have an attribute-based but camelCase API on the consuming side of React components — which we're already almost consistent at. And class="Button" is clearly more convenient than className="Button". In fact if DOM API was designed today it would probably let you set .class property precisely because the restriction on using class in an assignment like this was lifted in ES5 — almost ten years ago.

The only remaining big downside is the migration cost. We'll assess this carefully. But if we're doing a bunch of changes anyway, we might be able to make this one too and fix it for good. We're not thinking about this lightly and we take all your concerns into consideration.

Note: this might make sense to do for other React prop names that don't match camelCased attribute names. I'm thinking of htmlFor.

commented

@renatoagds

have plans for backward compatibility? Maybe follow the same path as React Fiber, adding warnings about the changes and giving time for large codebases update.

As I noted:

And we have more than 50 thousands components at Facebook to keep us honest about our migration strategy. We can't afford to rewrite product code except a few targeted fixes or automated codemods.

So we'll definitely try to make migration strategy as smooth as possible, just like we always do. If it's not smooth we won't be able to make the change ourselves.

re: className -> class, I'm cool with whichever decision, I can definitely see the exception to changing class for new users, and a side benefit of shorter lines of code. Although, they would still need to learn about the other camelCase names.

So the only inconvenience left with class is that you can't write this:

const { class } = props;

But I think that with time, this argument is not strong enough by itself. React doesn't force you to use > destructuring, and writing

const class = props.class;

isn't that much more effort.

Two (possibly small) things:

  1. Isn't const class = props.class invalid JavaScript? I didn't think that it was, and in a quick test Chrome doesn't like it. Also, this article suggests that it is not valid.

  2. I could see this change being a (once again, potentially small) area of frustration for folks that write components like this: nvm (see Update below)

const { oneProp, twoProp, className, ...rest }  = this.props;

// do stuff with oneProp, twoProp, className

return (
  <div
    someProp={prop}
    anotherProp={anotherProp}
    className={someClassName}
    {...rest}/>
);

After this change, this would need to be something like...

const { oneProp, twoProp, ...rest }  = this.props;

// do stuff with oneProp, twoProp, this.props.className

return (
  <div
    someProp={prop}
    anotherProp={anotherProp}
    {...rest}
    class={someClassName}/>
);

It's not impossible to work around this change, but it is a little bit more to keep in mind when both writing and reading components in this style.

Update:

Nevermind,

const { class: className } = this.props;

does the trick.

The only remaining big downside is the migration cost. We'll assess this carefully. But if we're doing a bunch of changes anyway, we might be able to make this one too and fix it for good. We're not thinking about this lightly and we take all your concerns into consideration.

Luckily this is easily mitigated if one is using a CSS-in-JS approach, like Aesthetic. Thanks for the amazing write up!

Random tip regarding attribute names, I found the excellent project, svg2jsx is great for converting large SVGs to use in React!

commented

@jamesplease Sorry, my mind blanked — you're right. Edited.

@jamesplease you are right. That also comes up frequently working with JSON, for the default value, so annoying!

const { default: defaultVal } = property

While you're changing the event system, it would be really nice to see something similar to Inferno's linkEvent function so we can do event handling using props in functional components without having to create an anonymous function each render.

className -> class will be a huge change for the ecosystem, numerous unmaintained components will become incompatible, and there will be nothing you can do if you cannot patch them. Maybe have some wrapper like StrictMode that disables this change for the components deeper in the tree to provide a gradual migration path?

commented

I could see this change being a (once again, potentially small) area of frustration for folks that write components like this:

const { oneProp, twoProp, className, ...rest }  = this.props;

// do stuff with oneProp, twoProp, className

return (
 <div className={someClassName} {...rest}/>
);

Just don't destructure it.

const { oneProp, twoProp, ...rest }  = this.props;

return (
  <div
    {...rest}
    class={'something ' + rest.class}
  />
);

@gaearon

whether React uses properties or attributes internally is an implementation detail

That seems like a problem too, honestly. DOM elements can and do behave differently in some cases depending on whether you're setting attributes or properties. React can't possibly know about all of the differences, but users of elements can know about the elements that they use. Explicit control over properties, attributes and events would let authors break out of this situation.

commented

@justinfagnani If you have specific things you'd like us to change, do you mind filing a separate issue with an API you suggest? This sounds a bit out of scope of this issue.

commented

@sompylasar

className -> class will be a huge change for the ecosystem, numerous unmaintained components will become incompatible, and there will be nothing you can do if you cannot patch them. Maybe have some wrapper like StrictMode that disables this change for the components deeper in the tree to provide a gradual migration path?

I agree — and we're still weighing pros and cons. Nothing is finalized.

But in practice, the problem of unmaintained components is not new — it comes up every React major release because something changes in majors by defintion (we can't move forward otherwise, and we don't have the luxury of keeping all legacy code in the bundle forever unlike in e.g. server environments). The same problem came up when PropTypes moved into a separate package, and will happen again with the UNSAFE_ lifecycle renaming. Unmaintained packages get forked or patched. We realize it's a big time sink, and this is why we avoid doing more than one big major in a year. For folks who can't afford to help, typically waiting a few months before moving to a new major is the best strategy because early adopters pave the way and revive the abandoned packages. Then we move forward all together.

Again — I very much understand your hesitation, but this is not principally different from other breaking changes that happened in the past, or that might happen in the future. As always, we'll put a lot of effort and emphasis on automated scripts you can run to convert most of your codebase, and that you can run on other packages as well (and send PRs to them — or fork them in their last state).

Regardless of your decision, one more argument against class is searchability. How many false positives will give you searching by class when you wanted to find components that use CSS classes in ES6 code that uses JS class components? Yes, you can search for class={, but what about destructuring of props in JSX that are created as an object in JS? (I'm against heavy use of props destructuring but they are still used) Of course, we need better context-aware AST-based search tools in code editors, but for now we only have text and regexp. Of course type systems may help to track object passing, but large population hasn't adopted them.

Something about using a reserved word just doesn't sit right with me, even if it doesn't cause any issues now; can we say for sure that rest.class (for example) won't be significant to the language in x years?

commented

@GeordieP If it works today it can't break tomorrow. That's the core principle of how JavaScript is being evolved, and the reason for many its idiosyncrasies.

@gaearon Fair enough, then. If it's a big enough win, I say go for it.

commented

@sompylasar I usually search for className= or className:, seems like both of these would work with class too.

Please get rid of className, and for god's sake, htmlFor. I am not a DOM developer, usually there is something very very wrong if I have to access native DOM methods. The biggest challenge I have onboarding people to React is the abstraction JSX makes over the DOM and its weird replacement HTML attributes. Everything is being transpiled, no reason to worry about reserved words at this point. IMO.

Not sure this is adding anything to the existing discussion, but it seems like there should be a better reason to change className.

Is saving beginner React learners from a slightly unintuitive name worthy of everyone having to update their projects & behavior?

As someone who uses de-structuring liberally, having to remember this new exception to the rule is probably a larger mental hiccup than the rare occasion that I write class instead of className.

Also, wouldn't beginners still be confused by the vast amount of material (as blogs/repos/etc) that uses className currently?

Finally, as @sompylasar said, this hurts search-ability within my codebase.

Maybe this is a tabs vs spaces type argument, but I don't totally understand why this change is necessary. It seems like a large cost for little gain unless this is part of a larger shift in how you want to model the API over time. That said, I'll for sure be using whatever ya'll decide 😅.

A bit offtop but it's kinda sad that no one (as far as I know of) had the idea to make a html/css/svg -> jsx transfomer in order to ease migrations to React with so many trivial changes to map HTML attrs to React props.

@jxub - I built a HTML to JSX converter as part of a hackathon way back in 2014: https://magic.reactjs.net/htmltojsx.htm. I'm not sure if it handles SVG well, though. The hackathon project was to make a script that would "ajaxify" a plain HTML site by using React (https://github.com/reactjs/react-magic) and part of that required me to build a way to create a React component from a chunk of HTML, so I released the HTML to JSX part as a separate standalone page.

We still care about supporting IE11 but it's possible that we will not attempt to smooth over some of the existing browser differences — which is the stance taken by many modern UI libraries.

@gaearon - What's some examples of modern UI libraries that do not smooth over browser differences? For me, that's one of the major reasons to use a library.

Conspiracy theory: this entire className / class news is one bright controversial thing that everybody will immediately pay attention to and argue about. It's either to attract attention to the rework project as a whole, or distract from something bigger that is happening in the shadows, or give one thing that can be later retracted while the rest will be accepted, like in the following anecdote:

The great theatrical artist Tyshler, creating sketches of scenery, in the corner drawing a small green dog. And when one of the admissions committee asked: "I like everything, but where is this dog?", The artist with a sigh of regret plastered her.

The true reasons behind this change aren't clear, but they have already skyrocketed the popularity of this new upgrade project and the community buzz around React.

It would be nice if supporting Passive Event Listeners were within the scope of React Fire, which is an important feature on mobile.
#6436

Thanks for all your hard work on this, but please reconsider className -> class.

We were all once React newbies and className didn't prevent us from learning and loving React.

I remember when I'm using vue with jsx, they already had class not className, I disagree if className will be changed to class, because React is pioneer in Virtual DOM, and represent of DOM it self.

Attach events at the React root rather than the document

@gaearon Does this mean that in a testing environment I won't have to append elements to the document to be able to dispatch real browser events and have the handlers associated to them be called? Having to do this is very counter-intuitive and I'm sure has been the source of many developers unfamiliar with React's internals get confused, waste a lot of time writing tests and incurring in event simulation and poor testing practices.

Which takes me to another note, can we do something about react-dom/test-utils ? I'm specially interested in the possible removal of Simulate given all the issues associated to it that we all know, and of course do the necessary changes in react-dom itself so that it is truly not needed anymore. Could that be in scope?

/cc @kentcdodds

I love the direction and big picture view that React Fire is taking. So far those look like great changes to work towards.

Love most announced changes, but I'm sceptical about the className change.

React doesn't force you to use destructuring or using this specific variable name, and writing something like (...code snippet...) isn't that much more effort.

While it isn't much effort to write, in my current experience I'd expect it to be way harder to explain to other developers (especially developers from other languages). On the other hand in all the years we used React at our company I guess only one or two devs were confused by className and just accepted this as Reacts API for setting the class names within a couple of minutes.

(In my personal opinion while I love destructing, the renaming syntax sometimes feels weird in itself for beginners, because it is different than renaming in imports which looks quite similar and can be combined with things like default values. One could just not use destructing then, but that would be a big exception to all other code we currently write at our company. Experience from others May differ of course, but that is my view on the problem 🙃.)

great

Also sceptical about the className change. It's one of the most minor changes in the scheme of things, but it's attracting a massive chunk of the commenting discussion here.

Is it really worth spending that much political capital on a change, when there's so much other good stuff that's being announced?

From where I stand, if you're making a decision where part of the justification is "and writing something like... ...isn't that much more effort.", that decision has got to have a massive upside, and the className -> class change just doesn't in comparison to everything else that's been announced.

this will be a major advance on 🔥 React Fire

About class v/s className, I think we should remind ourselves that JSX ≠ React.

Since JSX is a DSL that's designed to look like HTML, it's best to keep it as close to HTML as possible. Granted it's called className in the DOM API, but most are using JSX probably because they don't want to deal with DOM API directly.

If it makes more sense for React's API to closely match DOM API, then I hope it's ok/possible to do the mapping in the transpilation:
<img src="avatar.png" class="profile" />React.createElement("img", { src: "avatar.png", className: "profile" }).

It would be very valuable to make JSX syntax a clean superset of HTML.

To add to what @mhenr18 has said.

Current state of things:

  • React is inconsistent
  • API is camelCase

Proposed state of things:

  • React is inconsistent
  • API is camelCase
  • className -> class

Perceived benefits:

If React was open sourced today, it seems like the pros of allowing class (closer conceptually to what most people expect, less typing for the most commonly used prop) outweigh the downsides (slightly more typing to intercept it — in which cases you'll probably just want the spread operator anyway).

Actual downsides:

  • the entire ecosystem that depends on className stops working (huge upgrade effort)
  • the entire vast body of books, tutorials, examples, code, posts, articles becomes ever so slightly invalid
  • the only valid JS remaining is additional typing: const {class: cls} = props. Every other possible use-case in plain JS becomes invalid
  • React API remains inconsistent, and breaks further assumptions (why htmlFor not for etc.)

If I were a product manager, my immediate reaction to the change would be: wat?

@gaearon You might have considered this already, please tag PRs with "React Fire" or a similar keyword.

Issues usually are tagged correctly, PRs sometimes don't have them. This helps potential contributors.
This is coming from my experience when I was trying to read through the git history looking for React Fiber and React Reconciler related commits during the whole Fiber development. It helps those of us who are who are trying to figure out whats happening and see if we can contribute in some ways.

I also think renaming className to class will cause such a big migration effort and problems for new devs.
The className attribute is so visible and heavily used that it will literally break all libraries relying on react.
And this is not enough, most tutorials will be broken. A new device copy & pasting from an article will be wondering "why is that not working, it says className is not a valid prop".
So the senior has to help and we have not gained anything, because we still have to explain why it does not work like you would expect.
And for real, explaining that you have to use className to define classes on the component takes under a minute and is easily understandable. Explaining why they changed from className to class to every developer takes much longer and results in more frustration.

All the efforts required for a single word.
It will not change anything in the way react behaves.
It will not boost the development of react-dom.
It will not increase productivity of devs working longer than a week with react.
It will just break everything.

Please think about it, is it really worth it?

I have been using babel-plugin-react-html-attrs for years and it has been serving me well, I don't think renaming className to class is a good idea. It's better achieved by a plugin like the one I mentioned.

commented

Wasn’t there a Babel plugin to handle the whole “class v className“ / “for v htmlFor“ situation?

I hope it's possible to support html attributes as-is while maintaining backward compatibility with the naming decisions already made.

The fact that there are babel plugins already to do the conversion, is perhaps evidence that it should be possible to support it in the core JSX transpiler itself. But making it an official spec would make things so much easier and reliable for everyone.

I'm not aware of React internals, so can't say much about the real feasibility. Only expressing how I think it "should be" in terms of user-friendliness.

Please reconsider className vs class. As it has been said above, the gain is almost inexistant but there are real downsides.

One being that for us using Haxe as a language to write react applications, this would not only be a breaking change but will simply prevent us from writing any react application.

class is a reserved keyword in most programming languages, and we could simply not manipulate this prop anymore, making react application near impossible (good luck creating a real one without manipulating classes). Same goes for htmlFor vs for, sadly (this one is really ugly but I'm grateful it exists).

Oh by the way, searchability... Imagine you google for "React class", you'll get mixed signals: React class components, React class attribute. You google for "React className", you'll get outdated documentation (people mentioned above the huge amount of upgrade work geterated by this change besides code upgrades).

Is the goal of this project to generate more work for the community and more noise and mixed signals for the Internet? I hope not.

Yes, the Web and JavaScript ecosystem struggles to maintain back-compat with stupid mistakes of the past, but this strive for back-compat is what allowed it to grow up to such scale without major fragmentation.

I understand that there's no progress without change, and myself adopted the early FB's motto of breaking things to move fast (also having a plan how to re-assemble them back in advance).

If you're being so persistent that this change is really needed, just tell the true reason for the change. It can't be the "hard to learn" thing, sounds too shallow for the mighty React Core team. You definitely should have something in mind.