From 31aa4a4f405860b795abd3e9729a78d2d2ee0b90 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 17 May 2026 10:52:43 +0000 Subject: [PATCH] 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. --- src/components/Toast/index.tsx | 29 ++++++++++++++++++++++++++--- src/components/Toast/index.web.tsx | 29 ++++++++++++++++++++++++++--- src/view/com/composer/Composer.tsx | 1 + 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx index bb163b152d..5e60bd7995 100644 --- a/src/components/Toast/index.tsx +++ b/src/components/Toast/index.tsx @@ -83,6 +83,13 @@ type PromiseToastOptions = Omit & { 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 = Omit & { */ export function promise( input: Promise, - {loading, success, error, ...options}: PromiseToastOptions, + { + loading, + success, + error, + loadingDelay = 0, + ...options + }: PromiseToastOptions, ): Promise { const id = nanoid() + let settled = false + let loadingShown = false const render = ( content: React.ReactNode, @@ -114,19 +129,27 @@ export function promise( ) } - 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 diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx index 8f2207ea3e..a51e7fedd6 100644 --- a/src/components/Toast/index.web.tsx +++ b/src/components/Toast/index.web.tsx @@ -83,6 +83,13 @@ type PromiseToastOptions = Omit & { 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 = Omit & { */ export function promise( input: Promise, - {loading, success, error, ...options}: PromiseToastOptions, + { + loading, + success, + error, + loadingDelay = 0, + ...options + }: PromiseToastOptions, ): Promise { const id = nanoid() + let settled = false + let loadingShown = false const render = ( content: React.ReactNode, @@ -115,19 +130,27 @@ export function promise( ) } - 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 diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index b263a1881a..f9ab4d6739 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -1035,6 +1035,7 @@ export const ComposePost = ({ })() Toast.promise(appViewReady, { + loadingDelay: 500, loading: (