Custom Hook — useFetch, useLocalStorage, useDebounce

~25 daq · React · Hooks — Ilg'or

Oldingi darsda custom hook nima ekanini o'rgandik. Bu darsda real loyihalarda eng ko'p ishlatiladigan uchta custom hook ni yozamiz. Har birini muammodan boshlaymiz.


useLocalStorage

Muammo

Foydalanuvchi tema tanladi — qorang'i rejim. Sahifani yangiladi — tema qaytib yorug' bo'ldi. Chunki useState sahifa yangilanganda tozalanadi.

localStorage — brauzerda ma'lumot saqlash. Sahifa yangilansa ham saqlanadi. Lekin localStorage bilan ishlash har safar bir xil kod:

// Har joyda takrorlanadi
const saqlangan = localStorage.getItem('tema');
const [tema, setTema] = useState(saqlangan ?? 'yorug');

function temaOzgartir(yangi) {
  setTema(yangi);
  localStorage.setItem('tema', yangi);
}

Yechim — useLocalStorage

// hooks/useLocalStorage.js
import { useState } from 'react';

function useLocalStorage(kalit, boshlangich) {
  const [qiymat, setQiymat] = useState(() => {
    // Sahifa yuklanganda localStorage dan o'qish
    try {
      const saqlangan = localStorage.getItem(kalit);
      return saqlangan ? JSON.parse(saqlangan) : boshlangich;
    } catch {
      return boshlangich;
    }
  });

  function saqlash(yangi) {
    try {
      // Funksiya berilsa — useState kabi ishlaydi
      const saqlanadigan = yangi instanceof Function ? yangi(qiymat) : yangi;
      setQiymat(saqlanadigan);
      localStorage.setItem(kalit, JSON.stringify(saqlanadigan));
    } catch {
      console.error('localStorage ga saqlashda xato');
    }
  }

  return [qiymat, saqlash];
}

useState(() => ...) — funksiya shakli. Sahifa yuklanganda bir marta ishlaydi, har render da emas. localStorage.getItem ni har render da chaqirish o'rniga — faqat bir marta.

Ishlatish — xuddi useState kabi:

function App() {
  const [tema, setTema] = useLocalStorage('tema', 'yorug');
  const [til, setTil] = useLocalStorage('til', 'uz');

  return (
    <div className={tema}>
      <button onClick={() => setTema(t => t === 'yorug' ? 'qorangu' : 'yorug')}>
        Tema: {tema}
      </button>
    </div>
  );
}

Sahifa yangilansa ham tema saqlanadi.


useDebounce

Muammo

Qidiruv maydoniga foydalanuvchi yozayapti. Har harf yozilganda server ga so'rov ketayapti. "React" so'zini yozish uchun 5 ta harf — 5 ta so'rov. Bu server ni ortiqcha yuklaydi.

Debounce — foydalanuvchi yozishni to'xtatgandan keyin ma'lum vaqt o'tib so'rov yuborish. Yozayotganda kutadi, to'xtatganda ishlaydi.

Foydalanuvchi: R → e → a → c → t  (yozmoqda)
Debounce:      kutadi... kutadi... kutadi... kutadi...
               500ms o'tdi → "React" bilan so'rov yuboradi

Yechim — useDebounce

// hooks/useDebounce.js
import { useState, useEffect } from 'react';

function useDebounce(qiymat, kechikish = 500) {
  const [kechiktirilgan, setKechiktirilgan] = useState(qiymat);

  useEffect(() => {
    // qiymat o'zgarganda — taymer o'rnatiladi
    const timer = setTimeout(() => {
      setKechiktirilgan(qiymat);
    }, kechikish);

    // qiymat yana o'zgarganda — eski taymer bekor qilinadi
    // yangi taymer o'rnatiladi
    return () => clearTimeout(timer);
  }, [qiymat, kechikish]);

  return kechiktirilgan;
}

Qanday ishlaydi? Foydalanuvchi "R" yozdi — 500ms taymer boshlandi. "e" yozdi — eski taymer bekor qilindi, yangi 500ms boshlandi. "React" yozib to'xtatdi — 500ms o'tdi, kechiktirilgan "React" ga o'zgardi.

function Qidiruv() {
  const [qidiruv, setQidiruv] = useState('');
  const kechiktirilganQidiruv = useDebounce(qidiruv, 500);

  // faqat kechiktirilganQidiruv o'zgarganda so'rov ketadi
  const { data } = useFetch(
    kechiktirilganQidiruv ? `/api/qidirish?q=${kechiktirilganQidiruv}` : null
  );

  return (
    <div>
      <input
        value={qidiruv}
        onChange={e => setQidiruv(e.target.value)}
        placeholder="Qidirish..."
      />
      {data?.map(m => <p key={m.id}>{m.nomi}</p>)}
    </div>
  );
}

Foydalanuvchi yozayotganda qidiruv har harf o'zgaradi — input yangilanadi. Lekin kechiktirilganQidiruv faqat 500ms to'xtagandan keyin o'zgaradi — so'rov faqat o'shanda ketadi.


useFetch — to'liq versiya

24-darsda oddiy useFetch yozdik. Real loyiha uchun biroz kuchliroq versiya:

// hooks/useFetch.js
import { useState, useEffect, useCallback } from 'react';

function useFetch(url) {
  const [data, setData] = useState(null);
  const [yuklanmoqda, setYuklanmoqda] = useState(false);
  const [xato, setXato] = useState(null);

  const olish = useCallback(async () => {
    if (!url) return; // url null bo'lsa — so'rov yuborma

    const controller = new AbortController();
    setYuklanmoqda(true);
    setXato(null);

    try {
      const res = await fetch(url, { signal: controller.signal });
      if (!res.ok) throw new Error(`${res.status}: ${res.statusText}`);
      const json = await res.json();
      setData(json);
    } catch (err) {
      if (err.name !== 'AbortError') setXato(err.message);
    } finally {
      setYuklanmoqda(false);
    }

    return () => controller.abort();
  }, [url]);

  useEffect(() => {
    olish();
  }, [olish]);

  // qayta yuklash imkoniyati
  return { data, yuklanmoqda, xato, qaytaYukla: olish };
}
function UserProfil({ id }) {
  const { data: user, yuklanmoqda, xato, qaytaYukla } = useFetch(`/api/users/${id}`);

  if (yuklanmoqda) return <p>Yuklanmoqda...</p>;
  if (xato) return (
    <div>
      <p>Xato: {xato}</p>
      <button onClick={qaytaYukla}>Qayta urinish</button>
    </div>
  );

  return <h2>{user?.ism}</h2>;
}

Xulosa

Custom hook lar — real loyihalarda juda ko'p ishlatiladi. Eng ko'p uchraydigan uchta:

useLocalStorage(kalit, boshlangich)
  → localStorage bilan useState kabi ishlash

useDebounce(qiymat, kechikish)
  → tez-tez o'zgaradigan qiymatni sekinlashtirish
  → qidiruv, resize, scroll uchun

useFetch(url)
  → server so'rovi + loading + error holatlari

Har birining g'oyasi oddiy — takrorlanuvchi mantiqni bir joyga chiqarish. Komponent faqat natijani ishlatadi.


Keyingi bo'lim

4-bo'lim: Component Patterns

26-dars: Conditional Rendering — if/else, &&, ternary, null qaytarish