Skip to content

Discussion: Synchronous State Management and Async React #13286

Closed
@markerikson

Description

@markerikson

The React team has put out a fair amount of info on the upcoming async React concepts, including time sliced rendering and Suspense. They've also been in contact with the various state management library teams (Redux, MobX, and Apollo) to give us an early heads-up on how these libraries might need to change to work correctly with async React.

At this point I personally feel like I still have only a partial understanding of what types of changes are needed. Quoting a discussion between Dan and myself in reduxjs/react-redux#898 (comment) :

To the best of my understanding, these are the problems that React-Redux faces when trying to work with async React:

  1. React's time-slicing means that it's possible for other browser events to occur in between slices of update work. React might have half of a component tree diff calculation done, pause to let the browser handle some events, and something else might happen during that time (like a Redux action being dispatched). That could cause different parts of the component tree to read different values from the store, which is known as "tearing", rather than all components rendering based on the same store contents in the same diff calculation.
  1. Because of time-slicing, React also has the ability to set aside partially-completed tree diffs if a higher priority update occurs in the middle. It would fully calculate and apply the changes from the higher-priority change (like a textbox keystroke), then go back to the partially-calculated low-pri diff, modify it based on the completed high-pri update, and finish calculating and applying the low-pri diff.
    In other words, React has the ability to re-order queued updates based on priority, but also will start calculating low-pri updates as soon as they're queued. That is not how Redux works out of the box. If I dispatch a "DATA_LOADED" action and then a "TEXT_UPDATED" action, the data load causes a new store state right away. The UI will eventually look "right", but the sequence of calculating and applying the updates was not optimal, because Redux pushed state updates into React in the order they came in.

There was also some relevant discussion on Twitter at https://twitter.com/dan_abramov/status/1010978574105567234 :

swyx: so if anything async react “absorbs into the platform” some tricky parts of state mgmt (more accurately, creates a priority queue for UI interactions, and pushes async deps to whatever cache lib people end up using). haha, i guess im too eagerly taking those for granted.

dan_abramov: yeah. For the best desired experience, it needs control over when to apply updates, and what version of state to render with. So if a lib wants to own that, it’s missing out (although could work by getting “deoptimized” to sync mode).

mweststrate: For inspiration: Is there an abstract write down / complete overview of which conceptual operations need to be supported to fully support async? Fork + replay changes made in forks suffices? (hints about conflicts resolution?)

dan_abramov: Paging acdlite who’s currently working out how to make Relay work with it
dan_abramov: I think the main idea is that either your thing is meant for UI state that changes with interactions (and then we need a way to “route” it through setState—so it needs to provide a reducer), or it's more like a data cache (in which case your own storage is fine if it’s immutable)
dan_abramov: There is a third option too (a mutable data cache) which is what Relay will end up being for now. But this means it “deopts” to sync mode in some cases.

sebmarkbage: Those are more downstream recommendations, not first principles. A first principles is that you need to be able to read consistent old versions so if a parent passes data, the child needs to be able to read the version that the parent passed even after it has changed later.
sebmarkbage: Another first principle is that you need to be able to make edits to old versions and also make the same edit on the latest version (rebase).

dan_abramov: How would you do it without letting React manage state for you? We don't explicitly tell libs “now is time to rebase”.

mweststrate: Would need some kind of hooks were React tells: I want to fork / rebase this prop?

The phrase "deopts to sync mode" was explained by Brian in #13186 (comment) :

State updates scheduled from componentDidMount or componentDidUpdate are processed synchronously and flushed before the user sees the UI update. This is important for certain use cases (e.g. positioning a tooltip after measuring a rendered DOM element). In the case we're describing, this means that users of your application will never even see the temporary stale value because React will process the new value (synchronously) before yielding.

That might sound like a good thing, but what if the re-render includes a lot of components or is slow for some other reason? Then it might impact the frame rate and cause your application to feel unresponsive. This is what we are referring to when we say that create-subscription de-opts to synchronous rendering mode in some cases.

Also, Andrew commented in https://twitter.com/acdlite/status/1015286450537951233 :

We've learned from our work making Relay interop with async React, will share more soon.

Finally, there's some related discussion in reduxjs/react-redux#890 .

So, at this point what I would particularly appreciate is further info on exactly what constraints a normally-synchronous state management lib like Redux or MobX needs to comply with in order to work correctly with async React behavior, and any suggestions on possible implementations. It would also be extremely beneficial if we could come up with some demo apps that specifically demonstrate problematic interactions between async React and synchronous state management logic, so that we can use those as points of reference for ensuring that new versions of our libraries work correctly going forward.

Paging @gaearon , @acdlite , @bvaughn , @timdorr , @jimbolla , @cellog , @mweststrate, @peggyrayzis , and @jbaxleyiii for their thoughts and participation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions