HextaUI

useDelayedLoading

Yükleme durumunu yalnızca iş gerçekten yavaşken gösterir, sonra hiç titremeyecek kadar uzun süre açık tutar.

loading
useDelayedLoading(loading)
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
import { useDelayedLoading } from "@/hooks/use-delayed-loading"

function Lane({ label, loading }: { label: string; loading: boolean }) {
  return (
    <div className="flex h-9 items-center justify-between gap-4 rounded-lg bg-muted px-3 text-sm">
      <span className="text-muted-foreground">{label}</span>
      <span className="flex size-4 items-center justify-center">
        {loading ? <Spinner /> : null}
      </span>
    </div>
  )
}

export function UseDelayedLoadingDemo() {
  const [loading, setLoading] = React.useState(false)
  const visible = useDelayedLoading(loading)
  const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined)

  React.useEffect(() => () => clearTimeout(timer.current), [])

  const load = (ms: number) => {
    clearTimeout(timer.current)
    setLoading(true)
    timer.current = setTimeout(() => setLoading(false), ms)
  }

  return (
    <div className="flex w-full max-w-xs flex-col gap-4">
      <div className="flex flex-col gap-2">
        <Lane label="loading" loading={loading} />
        <Lane label="useDelayedLoading(loading)" loading={visible} />
      </div>
      <div className="flex flex-wrap justify-center gap-2">
        <Button variant="outline" size="sm" onClick={() => load(80)}>
          80ms
        </Button>
        <Button variant="outline" size="sm" onClick={() => load(220)}>
          220ms
        </Button>
        <Button variant="outline" size="sm" onClick={() => load(1500)}>
          1.5s
        </Button>
      </div>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/use-delayed-loading.json

Hook'u ve bağımlı olduğu her şeyi projenize ekler.

import { useDelayedLoading } from "@/hooks/use-delayed-loading"
const { data, isFetching } = useQuery(query)
const showSpinner = useDelayedLoading(isFetching)

return showSpinner ? <Spinner /> : <Results data={data} />

Ham yükleme bayrağını verin ve döndürdüğü boolean'dan işleyin. Sıcak bir bağlantıdaki çoğu istek 150ms'den kısa sürede biter. Bunlar için spinner göstermek hiçbir şey göstermemekten kötüdür: bir iki kare çakar ve ilerleme değil bir aksaklık gibi okunur.

Hook iki kural uygular. Bir şey göstermeden önce delay kadar bekler; böylece daha önce biten iş hiç yükleme durumu göstermez. Gösterge görünür olduktan sonra en az minDuration kalır; böylece birkaç kare içinde belirip kaybolamaz.

İş şu kadar sürerAçıklama
80msHiçbir şey gösterilmez.
250ms150ms'de gösterilir ve 400ms asgari süreyle 550ms'e kadar tutulur.
900ms150ms'de gösterilir ve iş biter bitmez gizlenir.

400ms asgari süre kasıtlı bir durum olarak algılanacak kadar uzun, kimseyi yavaşlatmayacak kadar kısadır.

  • Gösterge hâlâ görünürken loading yeniden açılırsa basitçe görünür kalır. Gizle ve yeniden göster yoktur.
  • Girdiler değiştiğinde veya bileşen unmount olduğunda zamanlayıcılar temizlenir; böylece hiçbir şey gittikten sonra durumu güncellemez.
  • Sunucuda ve ilk işleme sırasında false döndürür; böylece hydration uyuşmazlığı eklemez.

Skeleton'lar

Skeleton'lar içeriğin yerini alır, bu yüzden çakma bir spinner'dan bile daha rahatsız edicidir. Burada ilk yükleme yavaştır ve skeleton'u gösterir. Sonraki yüklemeler önbellekten gelir ve hiç göstermez.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Skeleton } from "@/components/ui/skeleton"
import { useDelayedLoading } from "@/hooks/use-delayed-loading"

const people = ["Ada Lovelace", "Grace Hopper", "Alan Turing"]

export function UseDelayedLoadingSkeleton() {
  const [loading, setLoading] = React.useState(false)
  const [cached, setCached] = React.useState(false)
  const showSkeleton = useDelayedLoading(loading, {
    delay: 200,
    minDuration: 500,
  })

  const refresh = () => {
    setLoading(true)
    setTimeout(
      () => {
        setLoading(false)
        setCached(true)
      },
      cached ? 60 : 1200
    )
  }

  return (
    <div className="flex w-full max-w-xs flex-col gap-4">
      <ul className="flex flex-col gap-3">
        {people.map((name) => (
          <li key={name} className="flex h-5 items-center text-sm">
            {showSkeleton ? <Skeleton className="h-3 w-32" /> : name}
          </li>
        ))}
      </ul>
      <Button variant="outline" size="sm" onClick={refresh} disabled={loading}>
        {cached ? "Refresh (cached)" : "Refresh (slow)"}
      </Button>
    </div>
  )
}
const showSkeleton = useDelayedLoading(isLoading, {
  delay: 300,
  minDuration: 600,
})

Skeleton'lar veya bindirmeler gibi ekranın çoğunu kaplayan göstergeler için delay değerini yükseltin. Ödeme gibi her beklemenin onaylanması gereken eylemler için 0'a doğru düşürün. minDuration değerini yaklaşık 300ms üzerinde tutun.

  • <Spinner loading={...} /> ve <Button loading> bu zamanlamaları zaten kullanır. Başka bir şey işlediğinizde hook'a başvurun.
  • Düzen göstergenin belirmesiyle kaymasın diye, örneklerdeki gibi, göstergenin alacağı alanı koruyun.
  • Bir aria-busy veya durum mesajıyla birlikte kullanın. Hook yalnızca görsel olarak neyin gösterileceğine karar verir.
PropTürVarsayılan
loadingİşin şu anda sürüp sürmediği.
boolean–
options.delayYükleme durumunu göstermeden önce beklenecek milisaniye.
number150
options.minDurationYükleme durumu gösterildikten sonra görünür kalacağı asgari milisaniye.
number400
DöndürürAçıklama
booleanYükleme durumunun gösterilip gösterilmeyeceği. Sunucuda her zaman false.

loading prop'u üzerinden Spinner.