What happened to concurrent "mode"? #64
Replies: 2 comments 5 replies
-
All this is great but one thing that I have been worrying about is the risk of developers using concurrent features without realising that they're doing so because the concurrent feature is being used as an implementation detail of a library, for example. If some of the code is not concurrent-safe this could lead to unexpected bugs, especially if developers are not on the lookout for it being a concurrency issue because they don't think they're triggering concurrent features. Perhaps a warning if a concurrent feature is used outside of a StrictMode subtree would be helpful? |
Beta Was this translation helpful? Give feedback.
-
Isn't it the case that ReactDOM.render in React 18 has the same behavior as in React 17? If so, couldn't React 17 have been skipped and people who need legacy roots can leave them as ReactDOM.render in React 18? |
Beta Was this translation helpful? Give feedback.
-
Overview
React 18 will add new features such as
startTransition
,useDeferredValue
, concurrentSuspense
semantics,SuspenseList
, and more. To power these features, React added concepts such as cooperative multitasking, priority-based rendering, scheduling, and interruptions.These features unlock new performance and user experience gains by more intelligently deciding when to render (or stop rendering) subtrees in an app. The cost of these features is that code needs to be written in a way that is resilient to these new concepts.
To help users understand whether their code is compatible, React added a set of development warnings and behaviors, called
StrictMode
, which warns users of unsafe behavior and flushes out any bugs in development that would be an issue if your used concurrent features in production. We added StrictMode to React 16.3 to allow the community to prepare for the new features.This post is targeted to users who have followed the experimental branch closely and are wondering what happened to concurrent "mode". It will provide an overview for how the plan to roll out these features changed over time and explain what we mean when we say that that there is no concurrent "mode", only concurrent features.
What was concurrent "mode"
The original thinking for adding these features to React is that it would be the default behavior for all apps. That means, when you upgraded, you would turn on a concurrent "mode" for your entire app. This would opt-in all of your app to concurrent rendering. To help make sure this was safe, we planned to also turn on StrictMode by default. The thinking for this strategy is that users would get the performance benefits of concurrent rendering in their entire app just by upgrading.
However, after hearing feedback from the community about the impact of the change and concerns about gradually upgrading, it was clear that this strategy needed an incremental story. React 17 was the first step for this.
By allowing apps to use multiple React versions in the same app in React 17, users can incrementally convert entire roots to React 18 and leave legacy roots on React 17, rendering the same version as before. The flaw in this strategy is that roots can be large and sprawl hundreds or thousands of files in your app. So within a root, users need a way to incrementally adopt React 18.
Incremental attempt #1: blocking mode
The first strategy for gradually adopting concurrent mode was to have three modes:
With this strategy, users would first convert their existing Legacy Mode roots to Blocking Mode, and then from Blocking Mode to Concurrent Mode.
Blocking Mode would to enable all StrictMode warnings and dev mode behavior without any changes to update or Suspense semantics. This would allow users to see all of the issues in their root and fix them while their code continued to work in production. Once all of the issues were fixed, users could turn on Concurrent Mode without any issues.
The problem with Blocking Mode
We’ve found that there were a few problems with this strategy.
By enabling all StrictMode warnings in Blocking Mode, users will still be overwhelmed with warnings without a good strategy incrementally for fixing them. Though the app would still “work”, users wouldn’t have a good way to cut through the noise and begin fixing all of the issues forced in dev. This strategy for incremental conversion of roots turns out in practice to still be all or nothing.
We realized that apps get most benefits to concurrent rendering through using concurrent features. It's still beneficial to enable concurrent rendering by default (and maybe we will some day in the future), but most of the value is in concurrent rendering through APIs like startTransition or Suspense. That means, we don’t need to change the default semantics. Instead, user can opt-into concurrent rendering by using concurrent features, which allows them to incrementally test and upgrade the app to new semantics while getting the benefits of concurrent features.
With the above changes, the only difference between Blocking Mode and Concurrent Mode would be that Blocking Mode would have all warnings enabled by default. But if there's no concurrent rendering by default, then we don't need to turn on strict mode by default. Additionally, after doing more research we now believe that we’ve been overly cautious with concurrent warnings, expressing concerns too eagerly when most code will work, especially with the changes above.
For these reasons, we've realized that warning for every theoretical mistake in the entire app, without the error being possible by using a concurrent feature, we would be unnecessarily holding back migrations. This has led to a re-thinking of the migration strategy.
Concurrent Features
In the Plan for React 18, we shared that the new strategy is to allow users to upgrade React 18 with no changes to concurrency, and gradually adopt concurrent features to opt-in to concurrent rendering. In this strategy, there is no Blocking Mode and since we include the legacy (React 17) root in React 18, there's no need to include both versions in your app.
The way this works is essentially a hybrid of blocking mode and concurrent mode. By default, React will render updates synchronously, the same as before in Legacy or Blocking modes. This means that when you first upgrade, before using the new APIs, your app will render the same as before. The only difference is some out-of-the-box improvements not related to concurrency (like automatic batching).
Since the app is not concurrent by default, we also don't need to enable StrictMode by default. Instead, users can add StrictMode to their app inside of the smaller parts of the app they actually use concurrent features in.
At this point, once you've upgraded, you're essentially using "concurrent mode". In fact, the
createRoot
API still literally turns on a root flag to enable "concurrent mode". The difference now is that we've changed what "concurrent mode" means in React so that we don't actually concurrently render outside of concurrent features.So that's what we mean when we say "there is no concurrent mode, there are only concurrent features".
Beta Was this translation helpful? Give feedback.
All reactions