Defer pending toast in Toast.promise() via loadingDelay
When the AppView indexes a new post quickly, briefly flashing the spinner before it morphs into the success toast feels glitchy. Add a `loadingDelay` option to Toast.promise() so the loading toast is only rendered if the promise hasn't settled within the delay; otherwise we jump straight to success/error. Default is 0 (immediate, matching the existing behaviour). The composer opts in to a 500ms delay so the common fast path shows only the success toast.
This commit is contained in:
@@ -83,6 +83,13 @@ type PromiseToastOptions<T> = Omit<BaseToastOptions, 'type'> & {
|
||||
loading: React.ReactNode
|
||||
success: React.ReactNode | ((data: T) => React.ReactNode)
|
||||
error?: React.ReactNode | ((err: unknown) => React.ReactNode)
|
||||
/**
|
||||
* Delay (ms) before the loading toast is shown. If the promise settles
|
||||
* before this delay elapses, the loading toast is skipped entirely and the
|
||||
* success/error toast appears directly. Defaults to 0 (loading shown
|
||||
* immediately).
|
||||
*/
|
||||
loadingDelay?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,9 +99,17 @@ type PromiseToastOptions<T> = Omit<BaseToastOptions, 'type'> & {
|
||||
*/
|
||||
export function promise<T>(
|
||||
input: Promise<T>,
|
||||
{loading, success, error, ...options}: PromiseToastOptions<T>,
|
||||
{
|
||||
loading,
|
||||
success,
|
||||
error,
|
||||
loadingDelay = 0,
|
||||
...options
|
||||
}: PromiseToastOptions<T>,
|
||||
): Promise<T> {
|
||||
const id = nanoid()
|
||||
let settled = false
|
||||
let loadingShown = false
|
||||
|
||||
const render = (
|
||||
content: React.ReactNode,
|
||||
@@ -114,19 +129,27 @@ export function promise<T>(
|
||||
)
|
||||
}
|
||||
|
||||
render(loading, 'pending')
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) return
|
||||
loadingShown = true
|
||||
render(loading, 'pending')
|
||||
}, loadingDelay)
|
||||
|
||||
return input.then(
|
||||
data => {
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
const content = typeof success === 'function' ? success(data) : success
|
||||
render(content, 'success')
|
||||
return data
|
||||
},
|
||||
err => {
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
if (error !== undefined) {
|
||||
const content = typeof error === 'function' ? error(err) : error
|
||||
render(content, 'error')
|
||||
} else {
|
||||
} else if (loadingShown) {
|
||||
sonner.dismiss(id)
|
||||
}
|
||||
throw err
|
||||
|
||||
@@ -83,6 +83,13 @@ type PromiseToastOptions<T> = Omit<BaseToastOptions, 'type'> & {
|
||||
loading: React.ReactNode
|
||||
success: React.ReactNode | ((data: T) => React.ReactNode)
|
||||
error?: React.ReactNode | ((err: unknown) => React.ReactNode)
|
||||
/**
|
||||
* Delay (ms) before the loading toast is shown. If the promise settles
|
||||
* before this delay elapses, the loading toast is skipped entirely and the
|
||||
* success/error toast appears directly. Defaults to 0 (loading shown
|
||||
* immediately).
|
||||
*/
|
||||
loadingDelay?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,9 +99,17 @@ type PromiseToastOptions<T> = Omit<BaseToastOptions, 'type'> & {
|
||||
*/
|
||||
export function promise<T>(
|
||||
input: Promise<T>,
|
||||
{loading, success, error, ...options}: PromiseToastOptions<T>,
|
||||
{
|
||||
loading,
|
||||
success,
|
||||
error,
|
||||
loadingDelay = 0,
|
||||
...options
|
||||
}: PromiseToastOptions<T>,
|
||||
): Promise<T> {
|
||||
const id = nanoid()
|
||||
let settled = false
|
||||
let loadingShown = false
|
||||
|
||||
const render = (
|
||||
content: React.ReactNode,
|
||||
@@ -115,19 +130,27 @@ export function promise<T>(
|
||||
)
|
||||
}
|
||||
|
||||
render(loading, 'pending')
|
||||
const timer = setTimeout(() => {
|
||||
if (settled) return
|
||||
loadingShown = true
|
||||
render(loading, 'pending')
|
||||
}, loadingDelay)
|
||||
|
||||
return input.then(
|
||||
data => {
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
const content = typeof success === 'function' ? success(data) : success
|
||||
render(content, 'success')
|
||||
return data
|
||||
},
|
||||
err => {
|
||||
settled = true
|
||||
clearTimeout(timer)
|
||||
if (error !== undefined) {
|
||||
const content = typeof error === 'function' ? error(err) : error
|
||||
render(content, 'error')
|
||||
} else {
|
||||
} else if (loadingShown) {
|
||||
sonner.dismiss(id)
|
||||
}
|
||||
throw err
|
||||
|
||||
@@ -1035,6 +1035,7 @@ export const ComposePost = ({
|
||||
})()
|
||||
|
||||
Toast.promise(appViewReady, {
|
||||
loadingDelay: 500,
|
||||
loading: (
|
||||
<Toast.Outer>
|
||||
<Toast.Icon />
|
||||
|
||||
Reference in New Issue
Block a user