Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Batch async actions even if useTransition is unmounted #28078

Merged
merged 3 commits into from
Jan 25, 2024

Commits on Jan 24, 2024

  1. Allow HostRoot fiber to suspend

    The error handling path has an invariant that a fiber that errors must
    have a parent, or else the renderer will panic. This is because the
    HostRoot fiber is the only fiber without a parent, and the HostRoot is
    supposed to handle all errors that weren't captured by an inner
    error boundary.
    
    However, this check currently exists in the same path that handles
    suspending. Unlike errors, we shouldn't panic if the HostRoot suspends;
    it should behave the same as if a component in the shell suspended.
    
    To fix this, I moved the check into an error-handling specific path, so
    it doesn't affect the Suspense case. We should consider refactoring
    this a bit more to fork the two paths earlier in the program flow.
    
    I didn't add any tests, because there's no (idiomatic) way to trigger
    this scenario currently, but I'm about to submit a fix for async actions
    that does.
    acdlite committed Jan 24, 2024
    Configuration menu
    Copy the full SHA
    42a0d5b View commit details
    Browse the repository at this point in the history
  2. Batch async actions even if useTransition is unmounted

    If there are multiple updates inside an async action, they should all
    be rendered in the same batch, even if they are separate by an async
    operation (`await`). We currently implement this by suspending in the
    `useTransition` hook to block the update from committing until all
    possible updates have been scheduled by the action. The reason we did it
    this way is so you can "cancel" an action by navigating away from the
    UI that triggered it.
    
    The problem with that approach, though, is that even if you navigate
    away from the `useTransition` hook, the action may have updated shared
    parts of the UI that are still in the tree. So we may need to continue
    suspending even after the `useTransition` hook is deleted.
    
    In other words, the lifetime of an async action scope is longer than the
    lifetime of a particular `useTransition` hook.
    
    The solution is to suspend whenever _any_ update that is part of the
    async action scope is unwrapped during render. So, inside useState
    and useReducer.
    
    This fixes a related issue where an optimistic update is reverted
    before the async action has finished, because we were relying on the
    `useTransition` hook to prevent the optimistic update from finishing.
    
    This also prepares us to support async actions being passed to the
    non-hook form of `startTransition` (though this isn't implemented yet).
    acdlite committed Jan 24, 2024
    Configuration menu
    Copy the full SHA
    0ffbed7 View commit details
    Browse the repository at this point in the history
  3. Batch async updates to classes, roots too

    This implements the previous fix for class components and root-level
    updates, too. I put this in its own step because there are some extra
    changes related to avoiding a context stack mismatch.
    acdlite committed Jan 24, 2024
    Configuration menu
    Copy the full SHA
    f032dfe View commit details
    Browse the repository at this point in the history