adobe / adobe/react-spectrum

Toast docs `wrapUpdate` example produces unhandled AbortError rejections when toasts overlap

Open Beginner friendly
#10,337 1 comment 1 reaction 0 assignees View on GitHub
Dominant language
TypeScript
Stars
15.9k
Forks
1.6k
Avg merge
3d 9m
Merged PRs (30d)
59

Description

### Provide a general summary of the issue here

The Toast documentation and `starters/docs/src/Toast.tsx` recommend a `wrapUpdate` that wraps queue updates in `document.startViewTransition()`:

```js
wrapUpdate(fn) {
if ('startViewTransition' in document) {
document.startViewTransition(() => {
flushSync(fn);
});
} else {
fn();
}
}
```

Every `queue.add()` **and** every auto-dismiss triggers `wrapUpdate`, so whenever two toasts overlap, the second `startViewTransition` call preempts the first. Per the View Transitions spec, the skipped transition rejects its `ready` promise with `AbortError: Transition was skipped` (`finished` resolves for skipped transitions). The example attaches no rejection handlers, so apps following the docs get one unhandled promise rejection per overlapping toast.

### 🤔 Expected Behavior?

The documented pattern should not produce unhandled promise rejections under normal usage (two or more concurrent toasts, or a toast added while another auto-dismisses).

### 😯 Current Behavior

One uncaught `AbortError: Transition was skipped` per overlapping toast. Each transition also snapshots the entire document, so toast bursts queue continuous full-page snapshots.

### 💁 Possible Solution

Minimal docs fix — handle all three transition promises:

```js
const transition = document.startViewTransition(() => { flushSync(fn); });
transition.finished.catch(() => {});
transition.ready.catch(() => {});
transition.updateCallbackDone.catch(() => {});
```

Optionally, the example could also skip starting a new transition while one is in flight, applying `fn()` directly. This avoids back-to-back full-document snapshots during bursts, and is safe with respect to queue semantics: the queue mutates its state before notifying subscribers, and subscribers read live state via `useSyncExternalStore`, so applying a notification directly always converges on current state.

### 🔦 Context

Surfaced in production error tracking (console flooding) and as DOM-stability timeouts in downstream Cypress suites when pages fire paired "loading → success" toasts. Any consumer flow with ≥2 concurrent toasts hits it.

### 🖥️ Steps to Reproduce

1. Use the docs Toast starter unmodified (Vanilla CSS or Tailwind).
2. Click the `queue.add(...)` button twice quickly (or add two toasts with overlapping timeouts).
3. Observe uncaught `AbortError: Transition was skipped` in the console.

### Version

`react-aria-components@1.17.0` (`react-aria@3.48.0` / `react-stately@3.46.0`) — the example is still present on `main` starters.

### What browsers are you seeing the problem on?

Chrome (any View Transitions-capable engine)

### What operating system are you using?

macOS

### 🧢 Your Company/Team

dv01

Contributor guide

Open the contributing guide

Research direction

Start with starters/docs/src/Toast.tsx and compare its wrapUpdate example with the Toast documentation. Reproduce the issue by triggering two queue.add calls quickly, then update the documented pattern so overlapping transitions do not create unhandled AbortError rejections; done means the example remains functional without console rejections.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
documentation, frontend
Issue type
Documentation
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.