Custom Hook — useFetch, useLocalStorage, useDebounce
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