Allowing different descriptions based on promise state (loading/success/fail)
Im relying on the title+description format to add additional details to my toasts, and i cant see a way to change the description field based on the promise. Could this be possible?
+1
For me it works when passing a ref as the description and adjusting it in the error or success callbacks.
const description = ref('Long description wrapping over multiple lines. Long description wrapping over multiple lines.')
toast.promise(
doWork(),
{
richColors: true,
loading: 'Doing work...',
description,
success: () => 'Success',
error: (err) => {
description.value = 'Short Description.'
return 'Failure'
},
},
)
HOWEVER: When doing this the height of the toast is not adjusted accordingly and the toast gets a wrong height when hovering over with the mouse (see video). :/
https://github.com/user-attachments/assets/a369c028-c65e-4f86-b84e-808c4c91e505
For me it works when passing a
refas thedescriptionand adjusting it in the error or success callbacks.const description = ref('Long description wrapping over multiple lines. Long description wrapping over multiple lines.')
toast.promise( doWork(), { richColors: true, loading: 'Doing work...', description, success: () => 'Success', error: (err) => { description.value = 'Short Description.' return 'Failure' }, }, ) HOWEVER: When doing this the height of the toast is not adjusted accordingly and the toast gets a wrong height when hovering over with the mouse (see video). :/
Bildschirmaufnahme.2025-01-24.um.12.26.57.mov
Have you found any workarounds for this? I found the same issue if the description is null before the promise and then we add the description after.
@lahdekorpi my workaround was to use two different toasts like this:
try {
await showPromiseToast(
doWork(),
{
title: 'Doing work'
},
)
toast.success('Success', { richColors: true })
}
catch (err) {
toast.error('Error', { richColors: true })
}
Where showPromiseToast is a custom function that shows a loading toast (toast.loading()), but only shows it after to promise is still not resolved after ~100ms. So if the work is finished fast the user only sees the success or error toast.
And if the loading toast is shown it is at least show for ~500ms so that it does not feel like the UI is flickering too much.
showPromiseToast Function
import { toast } from 'vue-sonner'
const LOADING_TOAST_MIN_DURATION = 500
const LOADING_TOAST_DISMISS_DURATION = 200
type LoadingToastOpts = NonNullable<Parameters<typeof toast.loading>[1]>
export async function showPromiseToast<ResultT>(
promise: Promise<ResultT>,
opts: LoadingToastOpts & {
title: string,
},
) {
let loadingToastId: string | number = -1
let loadingToastShowedAt = -1
let isPromisePending = true
async function waitAndDismissLoadingToast() {
if (loadingToastId === -1) return
// else: The loading toast is currently showing.
//
// Wait at least 500ms before dismissing the loading toast.
// This prevents the loading toast from flickering if the promise resolves quickly.
const remainingWaitingTime = LOADING_TOAST_MIN_DURATION - (Date.now() - loadingToastShowedAt)
await new Promise(resolve => setTimeout(resolve, remainingWaitingTime))
toast.dismiss(loadingToastId)
// Wait again for the dismiss animation to finish. This prevents UI flickering when
// the potentially new toast is shown.
await new Promise(resolve => setTimeout(resolve, LOADING_TOAST_DISMISS_DURATION))
}
const returnPromise = promise
.finally(() => {
isPromisePending = false
})
.then(async (result) => {
await waitAndDismissLoadingToast()
return result
})
.catch(async (err) => {
await waitAndDismissLoadingToast()
throw err
})
// Wait at least 100ms before showing the loading toast.
// This allows us to avoid showing the loading toast for promises that resolve quickly.
//
// 100ms is persieved as instant by the user.
// Source: https://www.pubnub.com/blog/how-fast-is-realtime-human-perception-and-technology
//
await new Promise(resolve => setTimeout(resolve, 100))
if (isPromisePending) {
loadingToastId = toast.loading(opts.title, opts)
loadingToastShowedAt = Date.now()
}
return returnPromise
}
@lahdekorpi my workaround was to use two different toasts like this:
I tested this as well, but the issue with this is, it triggers a new animation instead of replacing the existing one, and is distracting.
@lahdekorpi thats true. I specifically build the showPromiseToast function to minimize these distractions if the promise resolves quickly, but this is not a perfect workaround and I think currently there is none.