Replies: 2 comments 5 replies
-
Thanks for the write-up. I like the idea of it. The examples you mentioned can be implemented with what we have right now. What improvements does concurrent mode bring over how the implementation of such examples would look like? From the react code of your login example, it seems it's this just abstracts over the tracking of Also, WASM threads support is available in all major browsers (source: see "Threads and atomics" under the roadmap table). Can we leverage that in the implementation of concurrent mode? There are async runtimes for concurrency (such as smol) that we may be able to use. |
Beta Was this translation helpful? Give feedback.
-
I find it hard to write down all my thoughts connected to this, so for now, just the questions I'm trying to answer and some sentences. Don't take it as my opinion on the feature itself: Who decides which updates are "urgent", and how can a user control it?It seems the answer is that yew decides which updates are urgent, which would just be asking for trouble, imo. Most convenient to implement, but also incredibly hard to use correctly for a user that wants precise control. What's the central mechanism that makes it work?My sketch of an answer revolves around a dom bundle that isn't mounted yet, and a way to get notified when that part of the dom is ready (finished first render). I do not yet believe that concurrent mode fixes the current quirks of suspense, rather I think a better low-level building block encoding what suspense is could do that and be reused to emulate concurrent mode in components that need it (all your examples). Enabling CM for struct components is currently hard because, as far as I can tell, you have to clone the component and its connected state. That just screams inconsistent state to me. Rather, I'd only want to have a duplicate the dom tree and replace it when a new one is ready. Suspense alone is not powerful enough for that, but CM tries to be too high-level without explaining details. |
Beta Was this translation helpful? Give feedback.
-
What is concurrent mode?
Concurrent mode means that multiple copies of a component state can exist concurrently.
One copy of the component state is the current state that is displayed on the page (referred to as Current State below).
Zero, One or multiple other state copies can exist in the background (referred to as Background State below) if there are updates that can be applied non-urgently.
The renderer can create copies at anytime, discard any background state if it has conflicted with the current state and retry non-urgent updates.
Why do we need concurrent mode?
Concurrent mode solves a series of issues in a generalised approach and improves browser responsiveness during large state updates.
Some issues concurrent mode solves:
Suspend a component without showing fallback (suspension can happen in the background)
Currently, it's possible to show a loading unified fallback screen while any number of the child components is fetching data.
This allows the loading screen to be abstracted away from each request and the developer will only need to implement as many loading fallback as they see fit.
login-pre-concurrent.mov
navigate-pre-concurrent.mov
However, a better user experience would be to not show a fallback but an indicator:
login-post-concurrent.mov
navigate-post-concurrent.mov
This is challenging as once a component becomes suspended, it will stop producing any VNode layout and hence is unable to respond to any user input / updates. If we want to keep a component shown, it needs to be able to respond to user input.
Solution
Whilst we cannot suspend it here, we can suspend it in a parallel universe (suspend a background state).
Automatically throttle expensive computation (state / props updates can happen in the background)
If the developer implements a user action that involves expensive computation tasks, this may cause the page to become sluggish.
prime-pre-concurrent.mov
Solution
There's an important difference between:
It is important to optimise the performance of rendering functions, however, sometimes it is unavoidable to have expensive computation in the rendering function (especially during initial rendering). By classifying an update as a non-urgent update, the renderer will delay the rendering of components until a time that the browser is not busy and reveals when the rendering completes.
With concurrent mode, we can do this automatically by classifying the update as non-urgent. So it happens in the background with an old state still shown. If the value updates again before an update completes, we can simply discard any old work and start over again.
prime-post-concurrent.mov
Consistent Suspense Behaviour
The suspense currently have a couple inconsistent / unintentional behaviour.
These are not impossible to fix without concurrent mode but require manual fixing / patching for all of them one by one while it can be fixed by classifying suspense revealing as a non-urgent update in concurrent mode.
How it works
While each of the issues mentioned above looks vastly different, they can be solved together with concurrent mode.
With concurrent mode, the renderer classifies updates into 2 categories:
Updates users expect to see immediately
e.g.: When one enters some text into a text field, they would expect to see it immediately.
Updates users do not expect to see immediately.
e.g.: Load a new page. It’s common to assume that there is some network latency between server and client.
Urgent Updates
When applying an urgent update, we need to update the page in blocking mode.
The browser will not be able to respond to any user input until a state update finishes.
Every pre-concurrent mode update is urgent.
Non-urgent Updates
When applying a non-urgent update, we apply this in a background copy of the component at a time the browser is not busy (e.g.: requestIdleCallback).
If there are multiple updates requested to be applied at the same time, we can apply them in small chunks so the browser stays responsive.

If in the meanwhile an urgent update happens, it will "rollback" instead of "commit".
The following steps are applied in the background:
The following steps are not performed until it becomes current:
Limitations & Caveats
Function Components
Concurrent Mode is expected to work with most (all?) function components automatically.
As all function component states are Rc'ed as long as interior mutability is not used.
With concurrent mode, Interior Mutability should only be accessed at effects stage as effects are guaranteed to run on a current copy.
Struct Components
For struct components, concurrent mode will not work out of the box.
We can still force all struct component updates to be committed urgently so they work as if concurrent mode does not exist and add a way to manually "fork" and "merge" component states.
Interior Mutability
This is likely a permanent tradeoff. Interior Mutability is not directly usable as there's no good / straight forward way to produce a copy for interior mutable states.
Users can either:
External State Management
This is mostly related to Yewdux or Bounce where Yew cannot produce a copy for them automatically.
The situation is kind of complicated here as the state management libraries itself can have side effects (e.g.: persistent).
We need a way to notify state management libraries which copy is current and which states stored inside of it are mutated during a transition in order for side effects to be committed properly.
This can also be solved by committing urgently in the short term.
Footnote
Code used in examples: https://github.com/futursolo/concurrent-demos/
Beta Was this translation helpful? Give feedback.
All reactions