React Fire: Modernizing React DOM · Issue #13525 · facebook/react (original) (raw)
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 (Stop syncing value attribute for controlled inputs #11896). This was originally added in React 15.2.0 via Properly set value and defaultValue for input and textarea #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: Form submit button has empty values in 15.2.0 #7179, Firefox validation triggers on input component render #8395, IE 11 and Edge no longer prompt to remember password on controlled form #7328, Date input with defaultValue regression in 15.2 #7233, backspace fails to clear values on input type='email' #11881, Bug: Backspace in input type="number" behaves badly in Blink #7253, Remove loose check on non-number controlled inputs. Fix trailing dot issue. #9584, Inputs should not mutate value on type conversion (when they stringify to the same thing) #9806, [#9712] fix value '.98' should not be equal to '0.98'. #9714, Use defaultValue instead of setAttribute('value') #11534, Use the same value synchronization function on number blur #11746, Do not assign node.value on input creation if no change will occur #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 (Attach event per react container root, rather than on 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 (Native event.stopPropagation outside of React root cuts out React events #8693, Attach event listeners at the root of the tree instead of document #8117, Event listener attached to document will still be called after calling event.stopPropagation() #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 ([RFC] onChange -> onInput, and don't polyfill onInput 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 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 (Play Nicely with The DOM Event System (because it's legacy anyway) #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
(Why are attribute names "class" and "for" discouraged? #4331, see also React Fire: Modernizing React DOM #13525 (comment) 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.