Skip to content

Handle rejected ViewTransition ready/finished promises #7906

Description

@David-0x221Eight

Which project does this relate to?

TanStack Router (@tanstack/react-router@1.170.18, with @tanstack/router-core@1.171.15 transitively installed)

Describe the bug

When a navigation enables viewTransition, RouterCore.startViewTransition calls document.startViewTransition(...) but discards the returned ViewTransition object.

Browsers may reject ViewTransition.ready or ViewTransition.finished for normal lifecycle conditions, including a document becoming hidden or a transition being superseded. Because the promises have no rejection handlers, these can become unhandled promise rejections even though the router commits the new location successfully.

One Chromium case is:

InvalidStateError: Transition was aborted because of invalid state

Complete minimal reproduction

Repository: https://github.com/David-0x221Eight/tanstack-router-view-transition-repro

Versions:

  • @tanstack/react-router@1.170.18
  • @tanstack/router-core@1.171.15 (transitive dependency)
  • react@19.2.8
  • vite@8.1.5

Steps in Chrome:

  1. Run npm install and npm run dev.
  2. Open the local page and the DevTools console.
  3. Click Arm reproduction.
  4. Switch to another browser tab, wait briefly, and return.

The app uses the real browser API. It never replaces, wraps, or mocks document.startViewTransition. It listens for the real visibilitychange event, then calls:

router.navigate({
  to: "/next",
  viewTransition: true,
});

while the document is genuinely hidden. Its unhandledrejection observer only displays the rejection and does not call preventDefault().

Observed result in Chrome:

  • /next commits and the destination content renders;
  • the page receives an unhandled rejection;
  • Chrome reports InvalidStateError: Transition was aborted because of invalid state in the DevTools console.

Expected behavior

Router should consume rejections from the lifecycle promises it creates. Expected transition aborts should not become global errors; unexpected failures should remain observable.

A possible shape is:

const transition = document.startViewTransition(startViewTransitionParams)
void transition.ready.catch(handleViewTransitionRejection)
void transition.finished.catch(handleViewTransitionRejection)

For example, the handler can ignore AbortError and the Chromium InvalidStateError above, while forwarding other errors to globalThis.reportError (or console.error when unavailable).

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