Skip to content

Instantly share code, notes, and snippets.

@bvaughn
Last active December 29, 2021 02:12
Show Gist options
  • Star 41 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save bvaughn/054b82781bec875345bd85a5b1344698 to your computer and use it in GitHub Desktop.
Save bvaughn/054b82781bec875345bd85a5b1344698 to your computer and use it in GitHub Desktop.
`useSubscription` and `useMutableSource` tearing and deopt behavior.

useSubscription and useMutableSource1 tearing and deopt behavior.

Mounting a new tree

The tree below represents a React application mounting. During mount, two components read from an external, mutable source. The first one (List) reads version 1 of that data and the second one (Item) reads version 2.

Deopt

useSubscription (legacy mode)

N/A.

useSubscription (concurrent mode)

N/A.

useMutableSource (concurrent mode)

N/A.

Tearing

useSubscription (legacy mode)

Such a mutation during render should not occur during legacy mode (since legacy rendering is synchronous).

useSubscription (concurrent mode)

useSubscription will not be able to detect the mutation during render, so the initial commit will tear. An update will be scheduled to fix the tearing, but not until a later frame2 (so the torn render will be temporarily visible).

useMutableSource (concurrent mode)

useMutableSource will detect the mutation during render and will throw to restart the render.


Updating a tree

The tree below represents a React application updating previously mounted components. During the update, two components read from an external, mutable source. The first one (List) reads version 2 of that data and the second one (Item) reads version 3.

Deopt

useSubscription (legacy mode)

Every component subscribed to the source will be rendered and committed individually, potentially resulting in multiple separate paint/layout operations3. These operations won't be visible to the user, since they would all be synchronous, but it would be less efficient than if they were batched (and may also delay other, more important work).

useSubscription (concurrent mode)

Every component subscribed to the source will be batched together into a single render.

useMutableSource (concurrent mode)

Every component subscribed to the source will be batched together into a single render.

Tearing

useSubscription (legacy mode)

Such a mutation during render should not occur during legacy mode (since legacy rendering is synchronous).

useSubscription (concurrent mode)

useSubscription will not be able to detect the mutation during render, so the update will commit with tearing. A subsequent update will be scheduled to fix the tearing, but not until a later frame2 (so the torn render will be temporarily visible).

useMutableSource (concurrent mode)

useMutableSource will detect the mutation between renders. In many cases, it may be ale to re-use the previous value for the current render, but if that is not possible it will throw and restart the render.


Mounting a new subtree

The tree below represents a React application rendering a new subtree into an existing app. In a previous render, some components in the app (List, Item) read from version 3 of an external, mutable source. Sometime before this update the source changed, so during the update a new component (Item) reads version 4.

Deopt

useSubscription (legacy mode)

Every component subscribed to the source will be rendered and committed individually, potentially resulting in multiple separate paint/layout operations3. These operations won't be visible to the user, since they would all be synchronous, but it would be less efficient than if they were batched (and may also delay other, more important work).

useSubscription (concurrent mode)

Every component subscribed to the source will be batched together into a single render.

useMutableSource (concurrent mode)

Every component subscribed to the source will be batched together into a single render.

Tearing

useSubscription (legacy mode)

Note that in most cases, such a mutation should not occur in legacy mode (since a mutation in the source would typically schedule a synchronous update). However, because subscriptions are added in a passive effect2 it is possible for a mutation to happen before they are attached.

useSubscription (concurrent mode)

useSubscription will not be able to detect the mutation between renders, so the update will commit with tearing. A subsequent update will be scheduled to re-render the previously mounted parts of the tree though.

useMutableSource (concurrent mode)

useMutableSource will detect the incompatible store versions and will throw to restart the render.


1 The useMutableSource API does not currently exist but an RFC for it will be posted soon.

2 This is done to support edge cases in legacy mode (see codesandbox.io/s/k0yvr5970o).

3 This can be avoided if the subscription source uses the unstable_batchedUpdates API to wrap its subscription triggering, although this is an unstable API so it's probably not suitable for a lot of cases.

@kwoncharles
Copy link

Thanks for the good explanation 👍

There is a typo in Updating a tree part :)

it may be "ale" to re-use

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment