Skip to content

Toast added to a paused queue does not properly pause on hover/focus #8131

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

Open
hejtmii opened this issue Apr 23, 2025 · 1 comment
Open
Labels
bug Something isn't working

Comments

@hejtmii
Copy link

hejtmii commented Apr 23, 2025

Provide a general summary of the issue here

When using multiple levels of toasts and adding toasts to a queue paused with pauseAll (explained in Context below), the toast gets to a state when it doesn't properly respond to hover/focus, and disappears after timeout even when hovered/focused

🤔 Expected Behavior?

Toast added to a paused queue should be in paused state after being added
After the queue resumes, hover/focus should properly pause the timer, not letting it timeout

😯 Current Behavior

When toast is added to a paused queue that gets later resumed, hover/focus doesn't pause it and it disappears even when hovered

💁 Possible Solution

We created a workaround by using a customized toast queue that knows when it is paused, and makes sure the newly added toast gets properly paused when the queue is paused.

Having said that, we are aware that the flushSync is somewhat hacky, as we can't easily influence the default timer being started in the mount of useToast hook

Ideally, the knowledge of the queue being paused should be somehow native, and the hook shouldn't start the timer in this state at all...

import { ToastQueue, ToastOptions } from '@react-stately/toast';
import { flushSync } from 'react-dom';

// We use a custom toast queue to handle the special case when new toast is added while the active toasts are paused
// Without this extra handling, toast added while toasts are paused doesn't respect the pause on hover/focus
export class CustomToastQueue<T> extends ToastQueue<T> {
  private _isPaused: boolean = false;

  pauseAll() {
    this._isPaused = true;
    super.pauseAll();
  }

  resumeAll() {
    super.resumeAll();
    this._isPaused = false;
  }

  add(content: T, options?: ToastOptions): string {
    let key: string = '';

    // Wait for the added toast to render and start its timer in useToast effect
    flushSync(() => {
      key = super.add(content, options);
    });

    if (this._isPaused) {
      // When all are paused, immediately pause the added toast to prevent premature closing
      this.visibleToasts.find((toast) => toast.key === key)?.timer?.pause();
    }
    return key;
  }
}

🔦 Context

In our app, we use several layers of toasts

  • Top level toast provider in app root + root toast region
    • Modal with modal fog
      • Local toast provider in the modal + local toast region

While the modal (local toast provider) is mounted (the underlying layer is not active), the underlying toasts are being paused with queue.pauseAll and upon unmount queue.resumeAll.

While we use only two layers at a time, it is technically possible to nest this behavior. Whenever there is a modal, the underlying layer pauses so that the user can get back to the original context. Some actions may finish just before opening modal, or asynchronously underneath the modal, that is why we decided to handle it with several layers, modal gets its own context...

The problem is that the modal may trigger an action that finishes just before the modal (its local toast provider) gets unmounted and the "confirmation" toast for the modal action that is supposed to appear in the top layer is being added during a paused state,

Because of this, its timer starts even when the parent state is paused, and subsequent resumeAll on the unmount of the local toast provider calls resume on a running timer.

This results in some internal inconsistency, leading to a situation that the hover/focus to a toast no longer pauses its timer, and the toast disappears after the given timeout even when hovered

🖥️ Steps to Reproduce

I extended an existing SnackBar demo that I found with pausing the queue, it is similar to what we use toasts for

https://codesandbox.io/p/sandbox/snackbar-joy-ui-feat-react-aria-forked-wqz56v?file=%2Fdemo.tsx

Open the code sandbox
Click on "Add snackbar that doesn't pause on hover"
Hover over the snackbar and wait
The snackbar hides even when hovered (FAIL)

Do the same with "Add regular snackbar"
The snackbar correctly pauses on hover (OK)

Note: The code just simulates the situation of adding toast to a paused queue, didn't want to complicate it with multiple levels of providers that we use

Version

3.0.1

What browsers are you seeing the problem on?

Chrome

If other, please specify.

No response

What operating system are you using?

Windows

🧢 Your Company/Team

Kontent.ai

🕷 Tracking Issue

No response

@yihuiliao
Copy link
Member

Seems like a bug. We'll need to figure out some way to handle this but don't quite have an idea of what that would look like right now.

@yihuiliao yihuiliao added the bug Something isn't working label Apr 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants