Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Fire: Modernizing React DOM #13525

Closed
gaearon opened this issue Aug 31, 2018 · 227 comments
Closed

React Fire: Modernizing React DOM #13525

gaearon opened this issue Aug 31, 2018 · 227 comments

Comments

@gaearon
Copy link
Member

@gaearon gaearon commented Aug 31, 2018


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.

@matteing
Copy link

@matteing matteing commented Aug 31, 2018

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

Great work!

@developit
Copy link
Contributor

@developit developit commented Aug 31, 2018

🙂

@erikras
Copy link

@erikras erikras commented Aug 31, 2018

Attention form library authors! 🤣

@LaurenceM10
Copy link

@LaurenceM10 LaurenceM10 commented Aug 31, 2018

Great!

@ryanflorence
Copy link
Contributor

@ryanflorence ryanflorence commented Aug 31, 2018

className → class is fantastic

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

@diegohaz
Copy link

@diegohaz diegohaz commented Aug 31, 2018

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

@tannerlinsley
Copy link

@tannerlinsley tannerlinsley commented Aug 31, 2018

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?

@solomonhawk
Copy link

@solomonhawk solomonhawk commented Aug 31, 2018

This is fantastic news, thanks @gaearon!

@felixfbecker
Copy link

@felixfbecker felixfbecker commented Aug 31, 2018

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?

@alexparish
Copy link

@alexparish alexparish commented Aug 31, 2018

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

@mknepprath
Copy link

@mknepprath mknepprath commented Aug 31, 2018

👏

@gaearon
Copy link
Member Author

@gaearon gaearon commented Aug 31, 2018

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).

@nhunzaker
Copy link
Collaborator

@nhunzaker nhunzaker commented Aug 31, 2018

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!

@kentcdodds
Copy link
Contributor

@kentcdodds kentcdodds commented Aug 31, 2018

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.

@gaearon
Copy link
Member Author

@gaearon gaearon commented Aug 31, 2018

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.

@erikras
Copy link

@erikras erikras commented Aug 31, 2018

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

@kentcdodds
Copy link
Contributor

@kentcdodds kentcdodds commented Aug 31, 2018

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.

@gaearon
Copy link
Member Author

@gaearon gaearon commented Aug 31, 2018

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.

@drcmda
Copy link

@drcmda drcmda commented Aug 31, 2018

@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.

@gaearon
Copy link
Member Author

@gaearon gaearon commented Aug 31, 2018

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.

@AlexGalays
Copy link

@AlexGalays AlexGalays commented Aug 31, 2018

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.

@jacobp100
Copy link

@jacobp100 jacobp100 commented Aug 31, 2018

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

@ryanflorence
Copy link
Contributor

@ryanflorence ryanflorence commented Aug 31, 2018

@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

@fforw
Copy link
Contributor

@fforw fforw commented Aug 31, 2018

What about htmlFor?

@phpnode
Copy link

@phpnode phpnode commented Aug 31, 2018

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.

@Pajn
Copy link

@Pajn Pajn commented Aug 31, 2018

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.

@caub
Copy link

@caub caub commented Aug 31, 2018

@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'}, '..')
@alexeybondarenko
Copy link

@alexeybondarenko alexeybondarenko commented Aug 31, 2018

Great plan! Nice to here that! 👏👏👏

@Technologeek
Copy link

@Technologeek Technologeek commented Jul 3, 2019

Will "React Flare" come as a part of the default React package or need an additional install considering the amount of API's it will ship with?

@gaearon
Copy link
Member Author

@gaearon gaearon commented Jul 4, 2019

We'd like to avoid unused code getting bundled. So the current intention is for it to be opt-in per API. Maybe separate entry points in a single package.

@jonathantneal
Copy link

@jonathantneal jonathantneal commented Jul 11, 2019

Will this mouse and touch event system leverage PointerEvent? I did not see a mention of this web standard in the previous update, so I just wanted to bring it to your attention.

Pointer events are DOM events that are fired for a pointing device. They are designed to create a single DOM event model to handle pointing input devices such as a mouse, pen/stylus or touch (such as one or more fingers). The pointer is a hardware-agnostic device that can target a specific set of screen coordinates. Having a single event model for pointers can simplify creating Web sites and applications and provide a good user experience regardless of the user's hardware.

And here is a direct link to the current browser compatibility.

@trueadm
Copy link
Member

@trueadm trueadm commented Jul 11, 2019

@jonathantneal Yes, the new system heavily use Pointer Events – with fallbacks to Mouse/Touch events when there's no support for Pointer Events.

@trusktr
Copy link

@trusktr trusktr commented Sep 26, 2019

I am concerned that #11347 was not addressed in this issue. React flunks https://custom-elements-everywhere.com.

@felixfbecker
Copy link

@felixfbecker felixfbecker commented Oct 28, 2019

Please consider shadow root when redesigning the event system - just attaching to the React root wouldn't solve most issues today, only attaching to the element (#9242, #15759, #13713, #11827)

@mikesherov
Copy link

@mikesherov mikesherov commented Nov 3, 2019

In this update: #13525 (comment) @gaearon mentions:

However, as we started removing parts of the event system that we thought were unnecessary or outdated, we discovered many edge cases where it was being very helpful and prevented bugs — even in modern browsers.

I was curious if a list of these edge cases are documented anywhere?

@sbusch
Copy link

@sbusch sbusch commented Jan 20, 2020

@gaearon now that Flare has gone out (SCNR), is there an updated plan (regarding the June 5th, 2019 update) how to proceed?

And like @trusktr, I also would like to get #11347 addressed here.

@marcthe12
Copy link

@marcthe12 marcthe12 commented Jun 21, 2020

Could be split polyfills into another bundle especially the one not relevant to major evergreen browsers.

@gaearon
Copy link
Member Author

@gaearon gaearon commented Aug 11, 2020

Hey all, it's been a while and we've tried some of these things on and off.

Let me give an update on each:

  • 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.

We still want to do this, but we've decided to "reserve" React 17 to have minimal possible breaking changes so that it can focus on the next item on this list. So this change will wait until React 18.

  • 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.

We're doing this in React 17. This turned out to be a huge chunk of work but thankfully it's finished.

  • 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.

We'll likely come back to this but it's unclear how much churn is worth doing here. So this is still TBD.

  • 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.

We've tried this early in 2019, and a truly minimal event system did not work out very well in our internal testing. There was quite a bit of cross-browser normalization React is doing that is still useful for people with older browsers, or in more niche areas like rich text input editors using contentEditable. That said, as a part of our work on attaching events to roots, we've removed a lot of abstraction from the event system so it's easier to understand and improve in the future. As a part of React 17, we're removing "event pooling" which has caused a lot of confusion, and we're also no longer bubbling the onScroll event. We will likely follow with stopping bubbling of media events in React 18, bringing React behavior closer to the browser one. We did save some bytes with the new event system, but they were taken by new features we're working on, so it won't lead to a bundle size decrease overall.

  • 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.

This was the most controversial part of the proposal. Since then, we released Hooks, which encourage writing function components. In function components, we generally suggest using destructuring for props, but you can't write { class, ... } because it would be a syntax error. So overall it's not clear that this is ergonomic enough to actually follow through with. I think it's plausible we'll revisit this in the future, or at least make class not warn and let people do what they want. But for now, we'll shelving this idea.

@gaearon gaearon closed this Aug 11, 2020
@morevolk-latei
Copy link

@morevolk-latei morevolk-latei commented Aug 12, 2020

Hi, it's a great article!
Just wanted to know if there is any plan in pipeline to reduce React-DOM prod size? For mobile applications, it is still an overhead as the browser will be parsing 100+ KB of React-DOM JS and then other modules. Then app-specific JS.
For content-rich pages, it is causing greater Blocking and greater TTI.

Any Idea when can we see such changes?

@gaearon
Copy link
Member Author

@gaearon gaearon commented Aug 12, 2020

@morevolk-latei In your measurements, how much time is spent parsing 100 KB of ReactDOM?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
You can’t perform that action at this time.