Closed gaearon closed 4 years ago
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.
@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 :)
LGTM
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.
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.
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.
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).
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.
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.
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.
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
.
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.
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.
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
.
@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:
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.
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!
@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?
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.
@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.
@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?
@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.
@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.
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:
Proposed state of things:
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:
className
stops working (huge upgrade effort)const {class: cls} = props
. Every other possible use-case in plain JS becomes invalidhtmlFor
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.
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.
For latest status, see an update from June 5th, 2019: https://github.com/facebook/react/issues/13525#issuecomment-499196939
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 (https://github.com/facebook/react/issues/11896). This was originally added in React 15.2.0 via https://github.com/facebook/react/pull/6406. It was very commonly requested because people's conceptual model of the DOM is that thevalue
they see in the DOM inspector should match thevalue
JSX attribute. But that's not how the DOM works. When you type into a field, the browser doesn't update thevalue
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: https://github.com/facebook/react/issues/7179, https://github.com/facebook/react/issues/8395, https://github.com/facebook/react/issues/7328, https://github.com/facebook/react/issues/7233, https://github.com/facebook/react/issues/11881, https://github.com/facebook/react/issues/7253, https://github.com/facebook/react/pull/9584, https://github.com/facebook/react/pull/9806, https://github.com/facebook/react/pull/9714, https://github.com/facebook/react/pull/11534, https://github.com/facebook/react/pull/11746, https://github.com/facebook/react/pull/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 (https://github.com/facebook/react/issues/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 (https://github.com/facebook/react/issues/8693, https://github.com/facebook/react/pull/8117, https://github.com/facebook/react/issues/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
toonInput
and donβt polyfill it for uncontrolled components (https://github.com/facebook/react/issues/9657). See the linked issue for a detailed plan. It has been confusing that React uses a different event name for what's known asinput
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 makeonInput
andonChange
work exactly how the DOM events do for uncontrolled components.Drastically simplify the event system (https://github.com/facebook/react/issues/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.
className
βclass
(https://github.com/facebook/react/issues/4331, see also https://github.com/facebook/react/issues/13525#issuecomment-417818906 below). This has been proposed countless times. We're already allowing passingclass
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 processclassName
(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.