LoadingState appears only after 300ms: fast API answers no longer flash the loader (packs screen and every other data screen)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 17:05:52 +03:30
parent ec1bfc721d
commit b11016c6ea
2 changed files with 15 additions and 19 deletions

View File

@@ -67,21 +67,6 @@ export function MainApp({ profile, grades, payment, onSignedOut }: Props) {
); );
} }
/**
* صفحه‌ها از قبل گرفته شده‌اند، ولی `lazy` حتی برای ماژولِ آماده هم یک رندرِ تهی می‌گیرد؛ نشان دادنِ
* «یه لحظه صبر کن» در همان یک-دو فریم همان پرشِ ریزِ بینِ صفحه‌ها بود. لودر فقط اگر بارگیری
* واقعاً طول کشید (شبکه‌ی کند، پیش از تمام شدنِ preload) پس از ۳۰۰ms ظاهر می‌شود.
* با مرورگر دیده نشده؛ فقط از روی کد.
*/
function DelayedLoading() {
const [show, setShow] = useState(false);
useEffect(() => {
const t = setTimeout(() => setShow(true), 300);
return () => clearTimeout(t);
}, []);
return show ? <LoadingState /> : null;
}
function Screens({ onSignedOut }: Pick<Props, 'onSignedOut'>) { function Screens({ onSignedOut }: Pick<Props, 'onSignedOut'>) {
const nav = useNav(); const nav = useNav();
const { route } = nav; const { route } = nav;
@@ -99,7 +84,7 @@ function Screens({ onSignedOut }: Pick<Props, 'onSignedOut'>) {
exit={{ opacity: 0, x: 18 }} exit={{ opacity: 0, x: 18 }}
transition={{ duration: 0.18 }} transition={{ duration: 0.18 }}
> >
<Suspense fallback={<DelayedLoading />}> <Suspense fallback={<LoadingState />}>
<RouteView route={route} onSignedOut={onSignedOut} /> <RouteView route={route} onSignedOut={onSignedOut} />
</Suspense> </Suspense>
</motion.div> </motion.div>

View File

@@ -1,4 +1,4 @@
import type { ButtonHTMLAttributes, CSSProperties, ReactNode } from 'react'; import { useEffect, useState, type ButtonHTMLAttributes, type CSSProperties, type ReactNode } from 'react';
import { ChevronRight, RotateCw } from 'lucide-react'; import { ChevronRight, RotateCw } from 'lucide-react';
import { SeyarMascot } from '@/mascots/SeyarMascot'; import { SeyarMascot } from '@/mascots/SeyarMascot';
import type { SeyarChar, SeyarMood } from '@/mascots/seyar'; import type { SeyarChar, SeyarMood } from '@/mascots/seyar';
@@ -130,8 +130,19 @@ function StateBox({ char, mood, text, children }: { char: SeyarChar; mood: Seyar
); );
} }
export function LoadingState({ text = 'یه لحظه صبر کن…', char = 'sigma' as SeyarChar }) { /**
return <StateBox char={char} mood="idle" text={text} />; * لودر فقط بعد از `delayMs` دیده می‌شود. پاسخِ API معمولاً زیرِ ۳۰۰ms می‌رسد و نشان دادنِ لودر برای
* همان چند فریم، پرشِ ریزِ «لودر ← محتوا» می‌ساخت؛ حالا تا آن لحظه جای خالی می‌ماند. اگر پاسخ
* کند باشد کاربر باز هم لودر را می‌بیند. در مرورگر با شبکه‌ی کند آزموده نشده.
*/
export function LoadingState({ text = 'یه لحظه صبر کن…', char = 'sigma' as SeyarChar, delayMs = 300 }) {
const [show, setShow] = useState(delayMs <= 0);
useEffect(() => {
if (delayMs <= 0) return;
const t = setTimeout(() => setShow(true), delayMs);
return () => clearTimeout(t);
}, [delayMs]);
return show ? <StateBox char={char} mood="idle" text={text} /> : null;
} }
export function ErrorState({ error, onRetry, text }: { error?: unknown; onRetry?: () => void; text?: string }) { export function ErrorState({ error, onRetry, text }: { error?: unknown; onRetry?: () => void; text?: string }) {