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
|
loading: React.ReactNode
|
||||||
success: React.ReactNode | ((data: T) => React.ReactNode)
|
success: React.ReactNode | ((data: T) => React.ReactNode)
|
||||||
error?: React.ReactNode | ((err: unknown) => 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>(
|
export function promise<T>(
|
||||||
input: Promise<T>,
|
input: Promise<T>,
|
||||||
{loading, success, error, ...options}: PromiseToastOptions<T>,
|
{
|
||||||
|
loading,
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
loadingDelay = 0,
|
||||||
|
...options
|
||||||
|
}: PromiseToastOptions<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const id = nanoid()
|
const id = nanoid()
|
||||||
|
let settled = false
|
||||||
|
let loadingShown = false
|
||||||
|
|
||||||
const render = (
|
const render = (
|
||||||
content: React.ReactNode,
|
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(
|
return input.then(
|
||||||
data => {
|
data => {
|
||||||
|
settled = true
|
||||||
|
clearTimeout(timer)
|
||||||
const content = typeof success === 'function' ? success(data) : success
|
const content = typeof success === 'function' ? success(data) : success
|
||||||
render(content, 'success')
|
render(content, 'success')
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
|
settled = true
|
||||||
|
clearTimeout(timer)
|
||||||
if (error !== undefined) {
|
if (error !== undefined) {
|
||||||
const content = typeof error === 'function' ? error(err) : error
|
const content = typeof error === 'function' ? error(err) : error
|
||||||
render(content, 'error')
|
render(content, 'error')
|
||||||
} else {
|
} else if (loadingShown) {
|
||||||
sonner.dismiss(id)
|
sonner.dismiss(id)
|
||||||
}
|
}
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
@@ -83,6 +83,13 @@ type PromiseToastOptions<T> = Omit<BaseToastOptions, 'type'> & {
|
|||||||
loading: React.ReactNode
|
loading: React.ReactNode
|
||||||
success: React.ReactNode | ((data: T) => React.ReactNode)
|
success: React.ReactNode | ((data: T) => React.ReactNode)
|
||||||
error?: React.ReactNode | ((err: unknown) => 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>(
|
export function promise<T>(
|
||||||
input: Promise<T>,
|
input: Promise<T>,
|
||||||
{loading, success, error, ...options}: PromiseToastOptions<T>,
|
{
|
||||||
|
loading,
|
||||||
|
success,
|
||||||
|
error,
|
||||||
|
loadingDelay = 0,
|
||||||
|
...options
|
||||||
|
}: PromiseToastOptions<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const id = nanoid()
|
const id = nanoid()
|
||||||
|
let settled = false
|
||||||
|
let loadingShown = false
|
||||||
|
|
||||||
const render = (
|
const render = (
|
||||||
content: React.ReactNode,
|
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(
|
return input.then(
|
||||||
data => {
|
data => {
|
||||||
|
settled = true
|
||||||
|
clearTimeout(timer)
|
||||||
const content = typeof success === 'function' ? success(data) : success
|
const content = typeof success === 'function' ? success(data) : success
|
||||||
render(content, 'success')
|
render(content, 'success')
|
||||||
return data
|
return data
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
|
settled = true
|
||||||
|
clearTimeout(timer)
|
||||||
if (error !== undefined) {
|
if (error !== undefined) {
|
||||||
const content = typeof error === 'function' ? error(err) : error
|
const content = typeof error === 'function' ? error(err) : error
|
||||||
render(content, 'error')
|
render(content, 'error')
|
||||||
} else {
|
} else if (loadingShown) {
|
||||||
sonner.dismiss(id)
|
sonner.dismiss(id)
|
||||||
}
|
}
|
||||||
throw err
|
throw err
|
||||||
|
|||||||
@@ -1035,6 +1035,7 @@ export const ComposePost = ({
|
|||||||
})()
|
})()
|
||||||
|
|
||||||
Toast.promise(appViewReady, {
|
Toast.promise(appViewReady, {
|
||||||
|
loadingDelay: 500,
|
||||||
loading: (
|
loading: (
|
||||||
<Toast.Outer>
|
<Toast.Outer>
|
||||||
<Toast.Icon />
|
<Toast.Icon />
|
||||||
|
|||||||
Reference in New Issue
Block a user