@akhatmirzo/ui

Hooklar

@akhatmirzo/hooks — 27 ta bog'liqliksiz, SSR'da xavfsiz React hook.

Komponentlar ichida qayta-qayta yoziladigan mantiq: kechiktirish, localStorage, media so'rovlar, klaviatura yorliqlari, sahifalash. Paket faqat react ga tayanadi (18 va undan yangi), serverda xato bermaydi va @akhatmirzo/ui siz ham ishlaydi.

pnpm add @akhatmirzo/hooks
import { useDebounce, useHotkeys, useLocalStorage } from '@akhatmirzo/hooks';
HookVazifasi
useToggletrue / false holat va almashtirgich
useDebounceQiymatni kechiktirish (qidiruv)
useDebouncedCallbackFunksiyani kechiktirish (avtomatik saqlash)
useControllableStateBoshqariladigan va boshqarilmaydigan holat
useLocalStoragelocalStorage bilan sinxron holat
useMediaQueryCSS media so'rov natijasi
useClickOutsideElement tashqarisiga bosish
useCopyToClipboardBuferga nusxalash
useIntersectionKo'rinish maydoniga kirish (cheksiz scroll)
useEventListenerTiplangan hodisa tinglovchisi
useResizeObserverElement o'lchamini kuzatish
useScrollLockSahifa scroll'ini qulflash (modal)
useFocusTrapFokusni konteyner ichida ushlash
useLongPressUzoq bosish
useDocumentVisibilityTab ko'rinib turibdimi
useIsClientGidratatsiyadan keyin true
useAsyncAsinxron funksiya holati
usePaginationSahifalash tugmalari
useHotkeysKlaviatura yorliqlari
useIntervalInterval (null — to'xtaydi)
useTimeoutBir martalik taymer
useCountdownTeskari sanoq (SMS kod)
useIdleFoydalanuvchi faol emasligi
useStepKo'p bosqichli jarayon (wizard)
useInfiniteScrollCheksiz scroll
usePreviousOldingi qiymat
useOnlineTarmoq holati

Next.js'da

Hooklar faqat client komponentlarda ishlaydi — faylning birinchi qatoriga 'use client'; qo'ying.

Holat

useToggle

function useToggle(
  initial?: boolean,
): readonly [value: boolean, toggle: () => void, setValue: Dispatch<SetStateAction<boolean>>];

Mantiqiy holat. toggle barqaror (har render'da bir xil), shuning uchun uni to'g'ridan-to'g'ri onClick ga yoki boshqa hooklarga berish mumkin. initial — standart false.

const [open, toggleOpen, setOpen] = useToggle();

<Button onClick={toggleOpen}>Filtrlar</Button>;
<Button onClick={() => setOpen(false)}>Yopish</Button>;

useControllableState

function useControllableState<T>(params: {
  value?: T;
  defaultValue?: T;
  onChange?: (value: T) => void;
}): [T, Dispatch<SetStateAction<T>>];

O'z komponentingizni ham boshqariladigan (value + onValueChange), ham boshqarilmaydigan (defaultValue) qilish uchun. value !== undefined bo'lsa — qiymat otadan keladi, aks holda ichki holat ishlatiladi. onChange ikkala rejimda ham, faqat qiymat haqiqatan o'zgarganda chaqiriladi. Kutubxonadagi Dialog, Tabs, Select shu tamoyilda ishlaydi.

Boshqarilmaydigan (defaultValue=3)

3

Boshqariladigan (0…20)

10
stepper.tsx
interface StepperProps {
  value?: number;
  defaultValue?: number;
  onValueChange?: (value: number) => void;
}

function Stepper({ value, defaultValue = 0, onValueChange }: StepperProps) {
  const [count, setCount] = useControllableState({ value, defaultValue, onChange: onValueChange });
  return <Button onClick={() => setCount((n) => n + 1)}>{count}</Button>;
}

<Stepper defaultValue={3} />; // o'zi boshqaradi
<Stepper value={qty} onValueChange={setQty} />; // ota boshqaradi

Tip haqida

defaultValue berilsa, qiymat tipi T. Berilmasa — T | undefined.

Kechiktirish

useDebounce

function useDebounce<T>(value: T, ms?: number): T;

Qiymatni kechiktiradi: oxirgi o'zgarishdan ms millisekund o'tgach yangilanadi (standart 300). Har bir harfga so'rov yubormaslik uchun — qidiruv maydonining klassik vazifasi.

Natija yozishni to'xtatganingizdan 400 ms keyin

Yangilangan
ToshkentSamarqandBuxoroXivaNamanganAndijonNukusTermiz
const [query, setQuery] = useState('');
const debounced = useDebounce(query, 400);

useEffect(() => {
  if (debounced) void api.get('/search', { query: { q: debounced } });
}, [debounced]);

useDebouncedCallback

function useDebouncedCallback<Args extends unknown[]>(
  fn: (...args: Args) => void,
  ms?: number,
): DebouncedFunction<Args>;

interface DebouncedFunction<Args> {
  (...args: Args): void;
  cancel: () => void;
  flush: () => void;
  isPending: () => boolean;
}

Funksiyani kechiktiradi. Qiymat emas, harakat kechiktirilishi kerak bo'lganda — masalan, avtomatik saqlash. Har doim eng so'nggi fn chaqiriladi (eski closure muammosi yo'q), unmount'da kutilayotgan chaqiruv bekor qilinadi. ms — standart 300.

Avtomatik saqlash: yozishni to'xtatgach 800 ms o'tib

    const save = useDebouncedCallback((text: string) => api.patch('/notes/1', { text }), 800);
    
    <Textarea onChange={(e) => save(e.target.value)} onBlur={save.flush} />;
    MetodNima qiladi
    cancel()Kutilayotgan chaqiruvni bekor qiladi
    flush()Kutilayotgan chaqiruvni darhol bajaradi
    isPending()Kutilayotgan chaqiruv bormi

    Brauzer

    useLocalStorage

    function useLocalStorage<T>(
      key: string,
      initial: T | (() => T),
    ): [value: T, setValue: (value: T | ((prev: T) => T)) => void, remove: () => void];

    localStorage bilan sinxron holat. Qiymat JSON qilib saqlanadi va sinxronlanadi: shu sahifadagi bir xil kalitli boshqa hooklar va boshqa brauzer tablari ham yangilanadi. remove kalitni o'chiradi — qiymat initial ga qaytadi.

    0

    Boshqa komponent ko'rayotgan qiymat: 0

    Sahifani boshqa tabda oching — qiymat sinxron o'zgaradi

    const [recent, setRecent, clearRecent] = useLocalStorage<string[]>('recent-searches', []);
    
    const remember = (query: string) =>
      setRecent((list) => [query, ...list.filter((q) => q !== query)].slice(0, 5));

    Xatolar otilmaydi: serverda initial qaytadi, storage taqiqlangan yoki to'la bo'lsa qiymat sahifa xotirasida saqlanadi (holat ishlayveradi), buzuq JSON — initial.

    createStorage bilan aralashtirmang

    createStorage (@akhatmirzo/core) qiymatni o'z o'ramida yozadi. Bitta kalitni ikkalasida ishlatmang.

    useMediaQuery

    function useMediaQuery(query: string, defaultValue?: boolean): boolean;

    CSS media so'rov mos keladimi — oyna o'lchami yoki tizim sozlamasi o'zgarganda yangilanadi. Serverda va matchMedia yo'q joyda defaultValue (standart false) qaytadi.

    ≥ 640px≥ 1024pxTizim: qorong'i mavzuKam animatsiyaSichqoncha (hover)
    const isMobile = useMediaQuery('(max-width: 639px)');
    const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
    const reducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
    
    return isMobile ? <Drawer>…</Drawer> : <Dialog>…</Dialog>;

    useClickOutside

    function useClickOutside(
      refs: RefObject<Element | null> | ReadonlyArray<RefObject<Element | null>>,
      handler: (event: PointerEvent) => void,
      enabled?: boolean,
    ): void;

    Berilgan element(lar)dan tashqarida pointerdown bo'lganda handler chaqiriladi. Bir nechta ref berish mumkin — masalan, tugma va panel: tugmaning o'ziga bosish "tashqari" hisoblanmaydi. enabled (standart true) — panel yopiq paytda tinglamaslik uchun.

    const triggerRef = useRef<HTMLButtonElement>(null);
    const panelRef = useRef<HTMLDivElement>(null);
    
    useClickOutside([triggerRef, panelRef], () => setOpen(false), open);

    DOM'dan olib tashlangan elementga bosish (masalan, bosilganda yo'qolgan menyu bandi) hisobga olinmaydi.

    useCopyToClipboard

    function useCopyToClipboard(resetMs?: number): {
      copied: boolean;
      copy: (text: string) => Promise<boolean>;
    };

    Matnni buferga nusxalaydi. copy muvaffaqiyatni qaytaradi va xato otmaydi. copied — resetMs davomida (standart 2000) true, keyin o'zi false ga qaytadi; 0 — qaytmaydi. Clipboard API ishlamasa, eski usulga (execCommand('copy')) o'tadi.

    pnpm add @akhatmirzo/hooks
    const { copied, copy } = useCopyToClipboard();
    
    <Button onClick={() => copy(promoCode)}>{copied ? 'Nusxalandi' : 'Nusxa olish'}</Button>;

    useIntersection

    function useIntersection<T extends Element = Element>(
      options?: IntersectionObserverInit & { enabled?: boolean },
    ): [ref: (node: T | null) => void, entry: IntersectionObserverEntry | null];

    Element ko'rinish maydoniga kirganini kuzatadi — cheksiz scroll va kech yuklash uchun. Callback ref qaytaradi, shuning uchun element keyinroq yoki shartli render bo'lsa ham kuzatiladi. enabled: false — kuzatish to'xtaydi (masalan, boshqa sahifa qolmaganda). IntersectionObserver yo'q muhitda entry — null.

    • Buyurtma #1000
    • Buyurtma #1001
    • Buyurtma #1002
    • Buyurtma #1003
    • Buyurtma #1004
    • Buyurtma #1005
    • Buyurtma #1006
    • Buyurtma #1007
    • Buyurtma #1008
    • Buyurtma #1009
    • Buyurtma #1010
    • Buyurtma #1011
    Yuklanmoqda Yuklanmoqda…
    const [sentinelRef, entry] = useIntersection<HTMLDivElement>({
      rootMargin: '200px',
      enabled: hasMore,
    });
    
    useEffect(() => {
      if (entry?.isIntersecting) loadMore();
    }, [entry, items.length]);
    
    return (
      <>
        {items.map(renderItem)}
        <div ref={sentinelRef} />
      </>
    );

    Nega items.length bog'liqlikda

    Sentinel ko'rinishda qolsa, entry o'zgarmaydi. Ro'yxat uzunligini bog'liqlikka qo'shsangiz, yangi sahifa yuklangach yana tekshiriladi.

    DOM va hodisalar

    DOM bilan ishlovchi hooklar RefObject, elementning o'zi yoki null qabul qiladi (null yoki bo'sh ref — hech narsa qilinmaydi). Ular window / document ga faqat effekt ichida murojaat qiladi, shuning uchun serverda xato bermaydi. Siz bergan callback'lar "eng so'nggi qiymat" ref'ida saqlanadi — har renderda yangi funksiya bersangiz ham qayta obuna bo'linmaydi.

    useEventListener

    function useEventListener<K extends keyof WindowEventMap>(
      type: K,
      handler: (event: WindowEventMap[K]) => void,
      options?: UseEventListenerOptions,
    ): void;
    function useEventListener(
      target: Window | Document | HTMLElement | RefObject<HTMLElement | null> | null | undefined,
      type: string, // nishonga mos: WindowEventMap / DocumentEventMap / HTMLElementEventMap
      handler: (event: Event) => void,
      options?: UseEventListenerOptions,
    ): void;
    
    interface UseEventListenerOptions extends AddEventListenerOptions {
      enabled?: boolean;
    }

    Tiplangan hodisa tinglovchisi: event turi nishon va hodisa nomidan o'zi chiqariladi. target undefined bo'lsa — window, null bo'lsa — tinglanmaydi. Qisqa shakl useEventListener('resize', fn) — window uchun va render paytida window ga tegmaydi (SSR uchun eng qulay). enabled: false — vaqtincha o'chirish; capture, passive, once — addEventListener ga uzatiladi.

    Oyna o'lchamini o'zgartiring yoki istalgan tugmani bosing

    Kenglik: —Oxirgi tugma: —
    useEventListener('resize', () => setWidth(window.innerWidth));
    useEventListener(document, 'visibilitychange', onVisibility);
    
    const buttonRef = useRef<HTMLButtonElement>(null);
    useEventListener(buttonRef, 'pointerenter', () => prefetch('/orders'));
    
    useEventListener('scroll', onScroll, { passive: true, enabled: open });

    useResizeObserver

    function useResizeObserver<T extends Element = Element>(
      target: RefObject<T | null> | T | null,
      options?: { box?: ResizeObserverBoxOptions; enabled?: boolean },
    ): { width: number; height: number } | null;

    Element o'lchamini kuzatadi. Bir kadr ichidagi bir nechta o'zgarish requestAnimationFrame bilan bitta yangilanishga birlashtiriladi, o'lcham o'zgarmasa qayta render bo'lmaydi. box — 'content-box' (standart), 'border-box' yoki 'device-pixel-content-box'. Birinchi o'lchovgacha, serverda va ResizeObserver yo'q muhitda — null.

    Keng…O'ng pastki burchakdan torting
    const ref = useRef<HTMLDivElement>(null);
    const size = useResizeObserver(ref);
    const columns = size && size.width < 480 ? 1 : 3;
    
    return (
      <div ref={ref} className="grid" style={{ gridTemplateColumns: `repeat(${columns}, 1fr)` }}>
        …
      </div>
    );

    useScrollLock

    function useScrollLock(
      locked: boolean,
      options?: { target?: RefObject<HTMLElement | null> | HTMLElement | null },
    ): void;

    locked true bo'lganda sahifa (standart document.body) yoki target scroll'ini qulflaydi — modal, drawer, mobil menyu uchun. Yo'qolgan scrollbar kengligi padding-right bilan qoplanadi, shuning uchun kontent sakramaydi. iOS va scroll zanjiri uchun overflow: hidden bilan birga overscroll-behavior: none qo'yiladi.

    Sahifa erkin
    const [open, setOpen] = useState(false);
    useScrollLock(open);

    Bir nechta qulf

    Bir vaqtda bir nechta komponent qulflasa (masalan, drawer ustida dialog), hisoblagich ishlaydi: scroll oxirgi qulf ochilgandagina qaytadi va asl inline uslublar tiklanadi.

    useFocusTrap

    function useFocusTrap(
      target: RefObject<HTMLElement | null> | HTMLElement | null,
      active: boolean,
      options?: {
        initialFocus?: RefObject<HTMLElement | null> | HTMLElement | null;
        returnFocus?: boolean;
      },
    ): void;

    active paytida fokus konteyner ichida qoladi: Tab oxirgi elementdan birinchisiga, Shift+Tab birinchisidan oxirgisiga aylanadi, tashqariga chiqqan fokus qaytariladi. Faollashganda initialFocus yoki birinchi fokuslanuvchi element (u ham bo'lmasa — konteynerning o'zi) fokuslanadi. O'chirilganda yoki unmount'da fokus avvalgi elementga qaytadi (returnFocus, standart true). Ichma-ich tuzoqlarda faqat oxirgi faollashgani ishlaydi.

    const panelRef = useRef<HTMLDivElement>(null);
    const inputRef = useRef<HTMLInputElement>(null);
    
    useFocusTrap(panelRef, open, { initialFocus: inputRef });
    useHotkeys('esc', () => setOpen(false), { enabled: open, enableOnInputs: true });

    Esc va ARIA sizning zimmangizda

    Hook faqat fokusni boshqaradi. Yopish tugmasi, Esc, role="dialog" va aria-modal ni o'zingiz qo'shing yoki tayyor Dialog / Drawer komponentlarini ishlating.

    useLongPress

    function useLongPress<T extends Element = Element>(
      callback: (event: PointerEvent<T>) => void,
      options?: {
        delay?: number; // 500
        onStart?: (event: PointerEvent<T>) => void;
        onCancel?: (event: PointerEvent<T>) => void;
        moveThreshold?: number; // 10
        preventContextMenu?: boolean; // true
        enabled?: boolean; // true
      },
    ): LongPressHandlers<T>;

    Uzoq bosishni aniqlaydi — sichqoncha, barmoq va qalam uchun bir xil (Pointer Events). Elementga yoyiladigan barqaror ishlovchilar qaytaradi. delay ms o'tgach callback chaqiriladi; undan oldin qo'yib yuborilsa, element chetidan chiqilsa yoki barmoq moveThreshold px dan ko'p siljisa (scroll) — bekor qilinadi va onCancel chaqiriladi. Sichqonchaning faqat chap tugmasi hisobga olinadi. Bosib turilganda brauzer kontekst menyusi bloklanadi (preventContextMenu: false — bloklamaslik).

    Kutilmoqda
    const handlers = useLongPress<HTMLLIElement>(() => setSelectMode(true), { delay: 600 });
    
    return <li {...handlers}>Buyurtma #1024</li>;

    Sensorli ekranda matn tanlanib bosishni buzmasligi uchun elementga user-select: none (select-none) bering. Ro'yxat elementlarida touch-action ni o'zgartirmang: sahifa scroll qilina boshlasa brauzer pointercancel yuboradi va bosish bekor bo'ladi — scroll'li ro'yxatda aynan shu kerak. touch-action: none (touch-none) ni faqat sahifani scroll qilishi kerak bo'lmagan elementlarga qo'shing.

    useDocumentVisibility

    function useDocumentVisibility(): 'visible' | 'hidden';

    Sahifa (tab) ko'rinib turibdimi. Foydalanuvchi boshqa tabga o'tganda polling, video yoki animatsiyani to'xtatish uchun. Serverda — 'visible'.

    Tab ko'rinib turibdi

    Faol vaqt: 0 s

    Boshqa tabga o'tib qayting — hisob to'xtab turadi

    const visibility = useDocumentVisibility();
    
    useEffect(() => {
      if (visibility === 'hidden') return;
      const id = setInterval(refreshOrders, 10_000);
      return () => clearInterval(id);
    }, [visibility]);

    useIsClient

    function useIsClient(): boolean;

    Serverda va gidratatsiya paytida false, gidratatsiyadan keyin — true. Faqat brauzerda ishlaydigan narsani (grafik, window o'lchami, sana "hozir") gidratatsiya xatosisiz ko'rsatish uchun. useSyncExternalStore ustida qurilgan — keyingi mount'larda darhol true.

    const isClient = useIsClient();
    
    return isClient ? <SalesChart /> : <Skeleton className="h-64" />;

    Ma'lumotlar

    useAsync

    function useAsync<T, Args extends unknown[] = []>(
      fn: (...args: Args) => Promise<T>,
      deps?: DependencyList,
      options?: { immediate?: boolean },
    ): {
      status: 'idle' | 'loading' | 'success' | 'error';
      data: T | undefined;
      error: unknown;
      run: (...args: Args) => Promise<T | undefined>;
    };

    Asinxron funksiya holati. immediate: true (standart) — mount'da va deps o'zgarganda o'zi ishga tushadi. immediate: false — faqat run() bilan. Faqat eng so'nggi chaqiruv natijasi qabul qilinadi: tez-tez bosilganda eski javob yangisining ustiga yozilmaydi. Unmount'dan keyin kelgan javob e'tiborsiz qoladi.

    idle
    // Sahifa o'zgarganda o'zi yuklaydi
    const { status, data, error } = useAsync(
      () => api.get<Order[]>('/orders', { query: { page } }),
      [page],
    );
    
    // Faqat tugma bosilganda
    const { run, status: saving } = useAsync((form: OrderForm) => api.post('/orders', form), [], {
      immediate: false,
    });
    • data — oxirgi muvaffaqiyatli natija. Qayta yuklash va xato paytida ham saqlanadi — ro'yxat yuklanish paytida bo'shab qolmaydi.
    • run eskirgan yoki xato chaqiruvda undefined qaytaradi va xato otmaydi; xato error da.

    usePagination

    function usePagination(params: {
      total: number;
      pageSize: number;
      page: number;
      siblings?: number;
    }): {
      pages: (number | 'ellipsis')[];
      pageCount: number;
      page: number;
      hasPrev: boolean;
      hasNext: boolean;
      from: number;
      to: number;
    };

    Sahifalash tugmalari ro'yxati: 1 … 4 5 6 … 20. Tugmalar soni barqaror (siblings * 2 + 5), shuning uchun sahifa almashganda panel "sakramaydi". Ko'p nuqta faqat 2 va undan ko'p sahifani yashirganda qo'yiladi.

    41–50 / 200 ta yozuv

    usePagination({ total: 200, pageSize: 10, page: 5 });
    // { pages: [1, 'ellipsis', 4, 5, 6, 'ellipsis', 20], pageCount: 20, page: 5,
    //   hasPrev: true, hasNext: true, from: 41, to: 50 }
    
    usePagination({ total: 200, pageSize: 10, page: 1 }).pages; // [1, 2, 3, 4, 5, 'ellipsis', 20]
    usePagination({ total: 50, pageSize: 10, page: 2 }).pages; // [1, 2, 3, 4, 5]
    usePagination({ total: 95, pageSize: 10, page: 99 }).page; // 10 — chegaraga qisildi
    PropTipStandartTavsif
    totalmajburiynumber—Jami yozuvlar soni
    pageSizemajburiynumber—Bir sahifadagi yozuvlar
    pagemajburiynumber—Joriy sahifa, 1 dan. Chegaradan chiqsa qisiladi
    siblingsnumber1Joriy sahifaning har ikki tomonidagi sahifalar soni

    from / to — joriy sahifadagi birinchi va oxirgi yozuv tartib raqami ("41–50 / 200"). Bo'sh ro'yxatda from — 0. Hook'siz hisoblash kerak bo'lsa (masalan, serverda) — xuddi shu parametrli sof funksiya getPagination bor.

    Klaviatura

    useHotkeys

    function useHotkeys(
      keys: string,
      handler: (event: KeyboardEvent) => void,
      options?: { enabled?: boolean; preventDefault?: boolean; enableOnInputs?: boolean },
    ): void;

    Global klaviatura yorliqlari. mod — macOS'da ⌘, boshqa tizimlarda Ctrl. Bir nechta kombinatsiya vergul bilan: 'mod+k, /'. Standart holatda input, textarea, select va contenteditable ichida ishlamaydi — foydalanuvchi yozayotgan matnga xalaqit bermaslik uchun.

    AltK?G

    Yuqoridagi tugmalardan birini bosing

    useHotkeys('mod+k', () => setPaletteOpen(true));
    useHotkeys('mod+s', () => save()); // brauzerning "Saqlash" oynasi ochilmaydi
    useHotkeys('esc', close, { enabled: open, enableOnInputs: true });
    PropTipStandartTavsif
    enabledbooleantruefalse — tinglamaydi (masalan, panel yopiq bo'lsa)
    preventDefaultbooleantrueMos kelganda brauzerning standart amalini to'xtatadi
    enableOnInputsbooleanfalseInput, textarea, select va contenteditable ichida ham ishlaydi

    Kombinatsiya yozish:

    • Modifikatorlar: mod, ctrl, meta (cmd), alt (option), shift.
    • Tugma nomlari: harf va raqamlar, esc, enter, space, up / down / left / right, del, comma, plus va KeyboardEvent.key ning boshqa qiymatlari (tab, f1 ...).
    • Belgilar: '?' yoki '/' kabi belgilar Shift bilan yozilsa ham ishlaydi — shift yozish shart emas.
    • macOS'da alt harfni o'zgartiradi (Alt+K → ˚), lekin alt+k baribir ishlaydi — fizik tugma tekshiriladi.

    Yorliqni tugma yonida ko'rsatish uchun Kbd dan foydalaning: <Kbd keys={['mod', 'K']} />.

    matchHotkey va isApplePlatform

    function matchHotkey(event: KeyboardEvent, keys: string): boolean;
    function isApplePlatform(): boolean; // serverda false

    useHotkeys ichidagi tekshiruv alohida: o'z onKeyDown ingizda yorliqni tekshirish uchun.

    <Input
      onKeyDown={(e) => {
        if (matchHotkey(e.nativeEvent, 'mod+enter')) submit();
      }}
    />

    Vaqt va jarayon

    useInterval

    function useInterval(callback: () => void, delay: number | null): void;

    callback ni har delay millisekundda chaqiradi. delay — null bo'lsa interval to'xtaydi, qaytib raqam bo'lsa — yana boshlanadi. Callback har renderda yangi bo'lsa ham interval qayta boshlanmaydi: har safar eng so'nggi funksiya chaqiriladi, shuning uchun ichida joriy holatni bemalol o'qish mumkin.

    00:00
    const [seconds, setSeconds] = useState(0);
    const [running, setRunning] = useState(true);
    
    useInterval(() => setSeconds((s) => s + 1), running ? 1000 : null);

    useTimeout

    function useTimeout(
      callback: () => void,
      delay: number | null,
    ): { reset: () => void; clear: () => void };

    callback ni delay millisekunddan keyin bir marta chaqiradi. Taymer mount'da va delay o'zgarganda boshlanadi, unmount'da bekor qilinadi. clear() — kutilayotgan chaqiruvni bekor qiladi, reset() — taymerni hozirdan boshlab qaytadan boshlaydi. delay: null — taymer qo'yilmaydi.

    // Xabar 4 soniyadan keyin yopiladi; sichqoncha ustida turganda — yopilmaydi
    const { reset, clear } = useTimeout(() => setVisible(false), 4000);
    
    <div onMouseEnter={clear} onMouseLeave={reset}>
      Saqlandi
    </div>;

    useCountdown

    function useCountdown(
      seconds: number,
      options?: { autoStart?: boolean; onEnd?: () => void },
    ): {
      remaining: number;
      isRunning: boolean;
      start: () => void;
      pause: () => void;
      reset: () => void;
      restart: () => void;
      formatted: string; // "0:59"
    };

    Teskari sanoq — SMS kodni qayta yuborish, taklif muddati, test vaqti uchun. Vaqt Date.now() farqi bilan hisoblanadi: brauzer fon tabdagi taymerlarni sekinlashtirsa ham, foydalanuvchi qaytganda qolgan vaqt to'g'ri ko'rinadi. formatted — "m:ss" ko'rinishida ("2:05", "0:59").

    +998 90 *** ** 67 ga yuborildi

    const { formatted, isRunning, restart } = useCountdown(59);
    
    <Button
      disabled={isRunning}
      onClick={() => {
        sendSms();
        restart();
      }}
    >
      {isRunning ? `Kodni qayta yuborish (${formatted})` : 'Kodni qayta yuborish'}
    </Button>;
    • start() — pauzadan davom ettiradi. Sanoq tugagan bo'lsa (remaining: 0), seconds dan qaytadan boshlaydi.
    • pause() — to'xtatib turadi, qolgan vaqt saqlanadi.
    • reset() — seconds ga qaytaradi va to'xtatadi; restart() — qaytaradi va darhol boshlaydi.
    • onEnd — sanoq 0 ga yetganda har bir yugurishda bir marta chaqiriladi.
    • seconds o'zgarsa joriy sanoqqa ta'sir qilmaydi — keyingi reset / restart da qo'llanadi.
    PropTipStandartTavsif
    autoStartbooleantrueMount'da o'zi boshlanadi
    onEnd() => void—Sanoq 0 ga yetganda

    OtpInput.ResendTimer

    Faqat "qayta yuborish" tugmasi kerak bo'lsa — tayyor OtpInput.ResendTimer bor. useCountdown — o'z ko'rinishingiz kerak bo'lganda.

    useIdle

    function useIdle(ms: number, options?: { events?: string[]; initialState?: boolean }): boolean;

    Foydalanuvchi ms millisekund davomida hech narsa qilmasa — true. Har bir faollik (sichqoncha harakati, tugma bosish, teginish, scroll, tabga qaytish) taymerni qaytadan boshlaydi va holatni false ga qaytaradi. Avtomatik chiqish yoki "Hali shu yerdamisiz?" oynasi uchun.

    FaolInternet bor

    5 soniya sichqoncha va klaviaturaga tegmang. Tarmoqni o'chirib ko'ring — holat darhol yangilanadi.

    const idle = useIdle(15 * 60_000);
    
    useEffect(() => {
      if (idle) logout();
    }, [idle]);
    PropTipStandartTavsif
    eventsstring[]['mousemove', 'keydown', 'touchstart', 'scroll', 'visibilitychange']Faollik hodisalari. visibilitychange — document'da, qolgani window'da
    initialStatebooleanfalseMount'dagi holat

    useStep

    function useStep(
      count: number,
      options?: { initial?: number },
    ): {
      step: number;
      next: () => void;
      prev: () => void;
      goTo: (step: number) => void;
      reset: () => void;
      isFirst: boolean;
      isLast: boolean;
      progress: number; // 0..1
    };

    Ko'p bosqichli forma (wizard) holati. Qadam 0 dan boshlanadi va doim 0..count - 1 oralig'ida: next() oxirgi qadamda, prev() birinchisida hech narsa qilmaydi, goTo() chegaraga qisiladi. progress — birinchi qadamda 0, oxirgisida 1. Stepper bilan juft ishlaydi.

    1. 1-qadam, 4 tadan: Savat
    2. 2-qadam, 4 tadan: Yetkazib berishManzil va vaqt
    3. 3-qadam, 4 tadan: To'lov
    4. 4-qadam, 4 tadan: Tasdiqlash

    1-qadam, 4 tadan:Savat

    const { step, next, prev, goTo, isFirst, isLast } = useStep(steps.length);
    
    <Stepper steps={steps} value={step} onValueChange={goTo} />
    <Button disabled={isFirst} onClick={prev}>Orqaga</Button>
    <Button onClick={isLast ? submit : next}>{isLast ? 'Buyurtma berish' : 'Davom etish'}</Button>

    useInfiniteScroll

    function useInfiniteScroll<T extends Element = HTMLDivElement>(options: {
      hasMore: boolean;
      loading: boolean;
      onLoadMore: () => void;
      rootMargin?: string; // '200px'
    }): { sentinelRef: (node: T | null) => void };

    Cheksiz scroll — useIntersection ustida tayyor yechim. Ro'yxat oxiriga sentinel element qo'ying: u ko'rinishga rootMargin qadar yaqinlashganda onLoadMore chaqiriladi. loading: true paytida qayta chaqirilmaydi, hasMore: false bo'lsa kuzatish to'xtaydi. Yuklash tugagach sentinel hali ham ko'rinib tursa (sahifa qisqa bo'lsa), keyingi sahifa o'zi yuklanadi — ekran to'lguncha.

    • Buyurtma #1000
    • Buyurtma #1001
    • Buyurtma #1002
    • Buyurtma #1003
    • Buyurtma #1004
    • Buyurtma #1005
    • Buyurtma #1006
    • Buyurtma #1007
    • Buyurtma #1008
    • Buyurtma #1009
    • Buyurtma #1010
    • Buyurtma #1011
    Yuklanmoqda Yuklanmoqda…
    const { data, loading, hasMore, loadNextPage } = useOrders();
    const { sentinelRef } = useInfiniteScroll({ hasMore, loading, onLoadMore: loadNextPage });
    
    return (
      <>
        {data.map((order) => (
          <OrderRow key={order.id} order={order} />
        ))}
        <div ref={sentinelRef}>{hasMore && <Spinner size="sm" />}</div>
      </>
    );

    usePrevious

    function usePrevious<T>(value: T): T | undefined;

    Qiymatning oldingi holati; birinchi renderda — undefined. Faqat qiymat o'zgarganda yangilanadi: boshqa sabab bilan qayta render bo'lsa ham oldingi qiymat saqlanadi (taqqoslash — Object.is).

    const prevPage = usePrevious(page);
    const direction = prevPage !== undefined && page < prevPage ? 'orqaga' : 'oldinga';

    useOnline

    function useOnline(): boolean;

    Brauzer tarmoqqa ulanganmi — navigator.onLine va online / offline hodisalari. Serverda va gidratatsiya paytida true qaytaradi, shuning uchun "Internet yo'q" ogohlantirishi sahifa yuklanganda bir lahzaga chiqib qolmaydi. Jonli namunasi — useIdle demosida.

    function OfflineBanner() {
      const online = useOnline();
      if (online) return null;
      return (
        <Alert banner color="warning">
          Internet aloqasi yo'q. O'zgarishlar saqlanmaydi.
        </Alert>
      );
    }

    Sahifada