WisarWisar
Dasturlash kitobi/11-QISM — React47 daqiqa

11.7-bob: Custom hooks (o'z hooklaringni yozish)

11-QISM — Frontend: React · 7-mavzu


1. Kirish va motivatsiya

11.5 va 11.6-boblarda React'ning o'rnatilgan (built-in) hooklarini — useState, useEffect, useContext, useRef, useReducer, useMemo, useCallback — chuqur o'rgandik. Endi React'ning eng nafis va eng kuchli imkoniyatiga o'tamiz: o'z hooklaringni yozishcustom hooks. Bu — hooklar inqilobining asl maqsadi edi. Class komponentlar davrida bir komponentdagi mantiqni (masalan "oynaning kengligini kuzatish" yoki "serverdan ma'lumot olish") boshqasiga qayta ishlatish juda og'riqli edi — buning uchun HOC (Higher-Order Component) yoki render props kabi murakkab, ichma-ich joylashtiruvchi naqshlar ishlatilardi ("wrapper hell"). Custom hooklar bu muammoni butunlay hal qildi: stateful mantiqni oddiy funksiyaga ajratasiz va uni istalgan joyda qayta ishlatasiz.

Eng yaxshi xabar — custom hook yozish uchun yangi hech narsa o'rganishing shart emas. Custom hook — bu shunchaki use bilan boshlanadigan, ichida boshqa hooklarni chaqiradigan oddiy funksiya. Siz allaqachon bilgan useState, useEffect'ni shu funksiya ichiga solib, uni useFetch, useLocalStorage, useToggle deb nomlaysiz — tamom, bu custom hook. Lekin oddiy ko'rinish ortida chuqur g'oya yotadi: custom hooklar mantiqni (logikani) ulashadi, state'ni emas — har bir chaqiruv o'zining mustaqil state'ini oladi. Bu nozik, lekin hal qiluvchi tushunchani aniq anglamasdan custom hooklarni noto'g'ri ishlatish mumkin.

Bu bob: mantiqni qayta ishlatish muammosi (HOC/render props tarixi custom hook yechimi), custom hook nima (use* funksiya, hooklarni chaqiradi), qoidalar (use prefiksi nega majburiy, Rules of Hooks amal qiladi), eng muhim tushuncha — custom hook logikani ulashadi, state'ni emas (har chaqiruv izolyatsiyalangan), komponentdan mantiqni ajratish jarayoni, qaytarish konvensiyasi (massiv vs obyekt), parametrlash (argument va deps), hooklarni birlashtirish (composition), va 20+ amaliy custom hook (useToggle, useCounter, usePrevious, useLocalStorage, useDebounce, useThrottle, useFetch (AbortController + refetch), useWindowSize, useOnClickOutside, useMediaQuery, useInterval, useEventListener, useCopyToClipboard, useIntersectionObserver, useAsync, useDisclosure, useForm, useAuth), hook vs oddiy util funksiya chegarasi, TypeScript bilan custom hook (generic, as const), sinash (renderHook), qachon ajratish kerakligi, va tashqi kutubxonalar (usehooks-ts, react-use, ahooks). Custom hooklarni amaliy kutubxona darajasida — mexanizmigacha — ochamiz.

O'xshatish: Custom hook — bu oshxonadagi o'z retseptlaringiz. React'ning built-in hooklari (useState, useEffect) — bular xom mahsulotlar (un, tuxum, yog'). Har safar "tort" pishirmoqchi bo'lganda, xom mahsulotlardan boshlash zerikarli va xatoga moyil. Shuning uchun bir marta retsept (custom hook) yoziladi — "useFetch: mana shunday qilib serverdan ma'lumot olinadi" — va keyin u butun ilovada (va boshqa loyihalarda) qayta ishlatiladi. Eng muhimi: retseptni ikki kishi ishlatsa, ikkita alohida tort chiqadi (bir-biriga aralashmaydi) — xuddi shunday, bitta custom hook ikki komponentda ishlatilsa, har biri o'zining mustaqil state'ini oladi. Retsept — bu jarayon (logika), tayyor tort emas (state). Va tajribali oshpazlar tayyor retsept kitoblaridan (usehooks-ts) ham foydalanadi — har narsani noldan yozmaydi.

Nega muhim?

  • DRY — bir marta yozilgan mantiq (fetch, localStorage, debounce) butun loyihada qayta ishlatiladi 9.1-bob.
  • Toza komponentlar — murakkab mantiqni hookga ajratsangiz, komponent faqat UI'ga qaraydi (o'qish oson).
  • Sinash oson — custom hook alohida sinaladi (komponentdan mustaqil).
  • Senior ko'nikma — yaxshi custom hook dizayni — toza React arxitekturasining belgisi; har real loyihada bor.

2. Nazariya — chuqur tushuntirish

2.1. Mantiqni qayta ishlatish muammosi (HOC/render props custom hook)

text
  MUAMMO: ikki komponent BIR XIL mantiqni ishlatadi (masalan oyna kengligini kuzatish)
   mantiqni qanday ULASHISH (komponentlar orasida)?

  ESKI YECHIM 1 — HOC (Higher-Order Component) — komponentni o'rovchi funksiya:
  const Enhanced = withWindowSize(MyComponent);   // o'rab, props sifatida beradi
   "wrapper hell": withAuth(withTheme(withData(Component))) — ichma-ich, chalkash

  ESKI YECHIM 2 — RENDER PROPS — funksiya prop orqali:
  <WindowSize>{width => <div>{width}</div>}</WindowSize>
   JSX'da ichma-ich joylashish ("pyramid of doom"), o'qish qiyin

  YANGI YECHIM — CUSTOM HOOK (oddiy funksiya):
  const width = useWindowSize();   // toza, yassi, ichma-ich emas, o'qish oson 

  ┌──────────────────────────────────────────────────────────┐
  │ HOC/render props: mantiqni ULASHISH uchun KOMPONENT qatlam│
  │ Custom hook: mantiqni ULASHISH uchun shunchaki FUNKSIYA   │
  └──────────────────────────────────────────────────────────┘

   Custom hook — "wrapper hell"siz mantiq ulashish (hooklarning asl maqsadi)

Mantiqni qayta ishlatish muammosi — custom hooklarning tug'ilish sababi. Ko'p komponent bir xil stateful mantiqni ishlatishi mumkin (oyna kengligini kuzatish, ma'lumot olish, forma boshqarish) — savol: bu mantiqni komponentlar orasida qanday ulashish? Hooklardan oldin ikki yechim bor edi: (1) HOC (Higher-Order Component) — komponentni o'rab, qo'shimcha props beradigan funksiya (withWindowSize(Component)); muammosi — ko'p HOC ichma-ich joylansa "wrapper hell" (withAuth(withTheme(withData(C))) — chalkash, debug qiyin). (2) Render props — funksiya prop orqali mantiq ulashish (<WindowSize>{w => ...}</WindowSize>); muammosi — JSX'da ichma-ich "piramida" (o'qish qiyin). Custom hook ikkalasini ham yengadi: mantiqni ulashish uchun komponent qatlami emas, shunchaki oddiy funksiyaconst width = useWindowSize() (yassi, toza, ichma-ich emas). Bu — hooklarning asl maqsadi: stateful mantiqni "wrapper hell"siz, oddiy funksiya chaqiruvidek qayta ishlatish. HOC va render props hali ham mavjud 11.13-bob, lekin mantiq ulashish uchun custom hook — zamonaviy, afzal yo'l.

2.2. Custom hook nima — use* funksiya

text
  CUSTOM HOOK — "use" bilan boshlanadigan, ichida HOOK chaqiradigan oddiy funksiya:

  function useToggle(initial = false) {        //  nom "use" bilan
    const [value, setValue] = useState(initial);   // ichida built-in hook
    const toggle = () => setValue(v => !v);
    return [value, toggle];                    // kerakli narsani qaytaradi
  }

  ISHLATISH (oddiy funksiya chaqiruvidek):
  const [isOpen, toggleOpen] = useToggle(false);

  CUSTOM HOOK = oddiy funksiya + bitta SHART: ichida hook ishlatadi
   agar ichida hook (useState/useEffect/...) bo'lmasa — bu oddiy yordamchi funksiya (use kerak emas)

  ┌──────────────────────────────────────────────────────────┐
  │ Oddiy funksiya: hisob qiladi (formatDate, sum) — "use" yo'q│
  │ Custom hook: ichida hook bor (state/effect) — "use" SHART  │
  └──────────────────────────────────────────────────────────┘

   Yangi sintaksis YO'Q — bilgan hooklarni funksiyaga solib, "use" deb nomlash yetarli

Custom hookuse bilan boshlanadigan, ichida bir yoki bir nechta hook chaqiradigan oddiy JavaScript funksiyasi. Misol: useToggle — ichida useState ishlatadi, toggle funksiyasi yaratadi, va [value, toggle] qaytaradi; uni const [isOpen, toggleOpen] = useToggle() deb ishlatasiz — xuddi built-in hookdek. Eng muhim haqiqat: yangi sintaksis yo'q — siz allaqachon bilgan hooklarni (useState, useEffect) shunchaki funksiyaga solasiz va uni use* deb nomlaysiz. Custom hook = oddiy funksiya + bitta shart: ichida hook ishlatadi. Agar funksiya ichida hook bo'lmasa (faqat hisob qiladi — formatDate, sum), bu custom hook emas, oddiy yordamchi funksiya (use prefiksini ishlatma). Demak chegara aniq: ichida useState/useEffect/boshqa hook bormi? Ha custom hook (use*); yo'q oddiy funksiya. Bu soddalik — custom hooklarning kuchi: o'rganish uchun yangi narsa yo'q, faqat tashkillash usuli.

2.3. Custom hook qoidalari — use prefiksi va Rules of Hooks

text
  QOIDA 1 — nom "use" bilan boshlansin (use + bosh harf): useToggle, useFetch
  NEGA: React (va ESLint) "use*" ni hook deb taniydi  Rules of Hooks'ni tekshiradi
   "use"siz nomda (getToggle) ESLint hookni ko'rmaydi  qoida buzilishini sezmaydi (xatar)

  QOIDA 2 — Rules of Hooks amal qiladi (11.5: 2.2):
  - custom hook FAQAT komponent yoki boshqa hook ichida chaqiriladi
  - custom hook ENG YUQORIDA chaqiriladi (shart/tsikl ichida emas)
   if (x) { const v = useToggle(); }     // shart ichida — taqiq
   const v = useToggle(); if (x) {...}    // tepada

  QOIDA 3 — custom hook ICHIDA hooklar ham Rules'ga bo'ysunadi (tepada, shartsiz)

  ┌──────────────────────────────────────────────────────────┐
  │ "use" prefiksi — shunchaki kelishuv EMAS, ESLint signali  │
  │  React Hooks ESLint plugin "use*"ni hook deb tekshiradi   │
  └──────────────────────────────────────────────────────────┘

   "use" bilan boshlanmasa — hook xatolarini ESLint TUTMAYDI (jim buglar)

Custom hook qoidalari — uchta. Qoida 1 — nom use bilan boshlansin (useToggle, useFetch): bu shunchaki kelishuv emas, texnik zaruriyat — React va eslint-plugin-react-hooks funksiyani use prefiksiga qarab hook deb taniydi va unga Rules of Hooks'ni qo'llaydi; agar usesiz nomlansa (getToggle), ESLint uni oddiy funksiya deb biladi va hook qoidalari buzilishini sezmaydi (jim, qiyin topiladigan buglar). Qoida 2 — Rules of Hooks amal qiladi (11.5: 2.2): custom hook faqat komponent yoki boshqa hook ichida, va eng yuqorida (shart/tsikl ichida emas) chaqiriladi — chunki u ichida hook ishlatadi, demak chaqiruv tartibi muhim. Qoida 3 — custom hook ichidagi hooklar ham Rules'ga bo'ysunadi (tepada, shartsiz). Asosiy xulosa: use prefiksi — ESLint uchun signal; usiz yozsangiz, hook qoidasi buzilishini avtomatik tekshiruv tutmaydi. Shuning uchun ichida hook bo'lgan har funksiya majburan use* deb nomlanishi kerak.

2.4. Custom hook LOGIKANI ulashadi, STATE'ni emas (eng muhim)

text
  ENG NOZIK TUSHUNCHA: custom hook MANTIQNI ulashadi, STATE'ni EMAS

  Ikki komponent bir xil hook ishlatsa — har biri O'Z mustaqil state'ini oladi:

  function ComponentA() {
    const [count, inc] = useCounter();   // count_A — ALOHIDA
  }
  function ComponentB() {
    const [count, inc] = useCounter();   // count_B — ALOHIDA (A'ga aloqasi yo'q)
  }
  // ComponentA'da inc qilsang  faqat count_A oshadi (count_B o'zgarmaydi)

  ┌────────────────────────────────────────────────────────────┐
  │ Custom hook — "retsept" (jarayon/logika), tayyor "tort" emas │
  │ Har chaqiruv  yangi, izolyatsiyalangan state nusxasi        │
  └────────────────────────────────────────────────────────────┘

  AGAR state'ni ULASHISH kerak bo'lsa (A va B BIR XIL count ko'rsin):
   custom hook YETMAYDI  Context 12.1-bob yoki global store (Redux/Zustand — 12.2, 12.5)

   Custom hook = qayta ishlatiladigan MANTIQ; ulashilADIgan STATE emas (chalkashtirma!)

Custom hook logikani ulashadi, state'ni emas — bu bobning eng muhim va eng ko'p chalkashtiriladigan tushunchasi. Ikki komponent bir xil custom hookni chaqirsa, ular mantiqni (jarayonni) ulashadi, lekin har biri o'zining mustaqil, izolyatsiyalangan state'ini oladi. Masalan, ComponentA va ComponentB ikkalasi useCounter() ishlatsa — count_A va count_B alohida (A'da inc qilsangiz faqat A'ning soni oshadi, B'niki tegmaydi). Bu xuddi "retsept" kabi: retsept (hook) — bu jarayon, har kim undan o'z alohida tortini (state) pishiradi. Demak agar siz state'ni ulashishni xohlasangiz (A va B bir xil count ko'rsin), custom hook yetmaydi — buning uchun Context (12.1, ma'lumotni daraxt bo'ylab ulashish) yoki global store (Redux/Zustand — 12.2, 12.5) kerak. Custom hook — bu qayta ishlatiladigan mantiq, ulashiladigan state emas (buni chalkashtirmaslik muhim). Bu farqni aniq tushunish — custom hooklarni to'g'ri ishlatishning va keyingi state-management mavzularining (12-QISM) kalitidir.

2.5. Komponentdan mantiqni ajratish (extraction jarayoni)

text
  CUSTOM HOOK YARATISH — komponentdagi mantiqni "ko'chirish" (extraction):

  OLDIN (mantiq komponent ichida — takrorlanadi, komponentni to'ldipadi):
  function Profile() {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
      fetch("/api/user").then(r => r.json()).then(d => { setData(d); setLoading(false); });
    }, []);
    return loading ? <Spinner/> : <div>{data.name}</div>;
  }

  KEYIN (mantiq useFetch hook'ga ko'chdi — komponent toza):
  function useFetch(url) {                        // 1. mantiqni funksiyaga ko'chir
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    useEffect(() => {
      fetch(url).then(r => r.json()).then(d => { setData(d); setLoading(false); });
    }, [url]);
    return { data, loading };                     // 2. kerakli narsani qaytar
  }
  function Profile() {
    const { data, loading } = useFetch("/api/user");   // 3. komponent faqat UI'ga qaraydi
    return loading ? <Spinner/> : <div>{data.name}</div>;
  }

   Komponent "NIMA ko'rsatish"ga qaraydi; hook "QANDAY olish/hisoblash"ni biladi (ajratish)

Komponentdan mantiqni ajratish — custom hook yaratishning amaliy jarayoni. Odatda mantiq avval komponent ichida yoziladi (useState + useEffect bilan ma'lumot olish), keyin u takrorlana boshlaganda yoki komponentni "to'ldirib" yuborganda — uni custom hookga ko'chirasiz (extraction). Uch qadam: (1) stateful mantiqni (useState/useEffect bloki) yangi use* funksiyaga ko'chir; (2) komponentga kerakli narsani return qil; (3) komponentda uni chaqirib, qaytganini ishlat. Natija: komponent "nima ko'rsatish"ga (UI) qaraydi, hook "qanday olish/hisoblash"ni biladi — mas'uliyat ajratiladi (9.1 SRP). Bu ajratish ikki foyda beradi: (1) komponent toza va o'qish oson bo'ladi (faqat UI mantig'i); (2) ajratilgan mantiq qayta ishlatiladi (boshqa komponentda useFetch("/api/products")). Custom hook yozishni "boshidan rejalashtirma" — avval komponentda oddiy yoz, takrorlanishni yoki murakkablashishni sezganda ajrat 2.13-bob.

2.6. Qaytarish konvensiyasi — massiv vs obyekt

text
  CUSTOM HOOK kerakli narsani qaytaradi — IKKI usul:

  MASSIV [] — kam element (1-2) + chaqiruvchi NOM bermoqchi bo'lsa:
  function useToggle() { ... return [value, toggle]; }
  const [isOpen, toggleOpen] = useToggle();      // istalgan nom (useState kabi)
  const [isDark, toggleDark] = useToggle();      // qayta — boshqa nom (qulay)

  OBYEKT {} — ko'p element YOKI ba'zilarini olish kerak bo'lsa:
  function useFetch() { ... return { data, loading, error, refetch }; }
  const { data, loading } = useFetch(url);       // faqat keraklisini ol (tartib muhim emas)
  const { data: users } = useFetch(url);         // qayta nomlash mumkin

  ┌──────────────┬────────────────────────────────────────────┐
  │ Massiv []    │ 1-2 qiymat, chaqiruvchi NOM beradi (useState)│
  │ Obyekt {}    │ 3+ qiymat, tanlab olish, o'z-o'zini hujjatlash│
  └──────────────┴────────────────────────────────────────────┘

   Massiv — pozitsiya bo'yicha (tartib muhim); obyekt — nom bo'yicha (tartib erkin)
   Ko'p qiymatli hookda obyekt afzal (data, loading, error — nomi o'zini tushuntiradi)

Qaytarish konvensiyasi — custom hook kerakli narsani qaytaradi, ikki usul bilan: massiv [] yoki obyekt {}. Massiv — kam element (1-2) bo'lganda va chaqiruvchi o'z nomini bermoqchi bo'lganda (xuddi useState kabi: const [isOpen, toggleOpen] = useToggle(), keyin const [isDark, toggleDark] = useToggle() — har chaqiruvda boshqa nom — qulay). Obyekt — ko'p element (3+) bo'lganda yoki faqat ba'zilarini olish kerak bo'lganda (const { data, loading } = useFetch(url) — faqat keraklisini olasiz, tartib muhim emas, kerak bo'lsa qayta nomlaysiz data: users). Farq: massiv — pozitsiya bo'yicha (tartib muhim — birinchi element value, ikkinchi setter), obyekt — nom bo'yicha (tartib erkin, o'zini hujjatlaydi). Amaliy qoida: 1-2 qiymat va nomlash kerak — massiv (useToggle, useCounter); 3+ qiymat (data, loading, error, refetch) — obyekt (nomlari o'zlarini tushuntiradi, tanlab olinadi). Ko'pchilik real custom hooklar (ayniqsa fetch/forma) obyekt qaytaradi.

2.7. Argument va parametrlash — hookni moslashuvchan qilish

text
  CUSTOM HOOK ARGUMENT oladi (oddiy funksiya kabi) — moslashuvchanlik uchun:

  function useFetch(url) {                        // url — argument
    useEffect(() => { fetch(url)... }, [url]);    //  argument deps'da (o'zgarsa qayta — 11.5: 2.6)
  }
  useFetch("/api/users");    useFetch("/api/products");   // turli url

  KO'P ARGUMENT / OPSIYA OBYEKT:
  function useFetch(url, { method = "GET", enabled = true } = {}) {
    useEffect(() => { if (enabled) fetch(url, { method }); }, [url, method, enabled]);
  }
  useFetch("/api/users", { enabled: isLoggedIn });   // shartli yuklash

   Argument effekt ichida ishlatilsa — deps'ga qo'shish shart (exhaustive-deps — 11.5: 2.6)
   Obyekt/funksiya argument — har chaqiruvda yangi havola  ehtiyot (useMemo/useCallback — 11.6: 2.9)

Argument va parametrlash — custom hook ham oddiy funksiya kabi argument oladi (moslashuvchanlik uchun). Masalan useFetch(url)urlni argument sifatida olib, turli endpoint'lar uchun ishlatiladi (useFetch("/api/users"), useFetch("/api/products")). Argument hook ichidagi effektda ishlatilsa, uni dependency array'ga qo'shish shart (}, [url]url o'zgarganda qayta ishlasin — 11.5: 2.6). Ko'p moslashuv kerak bo'lsa, opsiya obyekt ishlatiladi: useFetch(url, { method, enabled }) — masalan enabled: isLoggedIn bilan shartli yuklash. Ikki ehtiyotkorlik: (1) effektda ishlatilgan har argument deps'da bo'lsin (exhaustive-deps — 11.5: 2.6); (2) agar argument obyekt yoki funksiya bo'lsa, u chaqiruvchida har render yangi havola olishi mumkin (11.6: 2.9) — bu hook ichidagi effektni keraksiz qayta ishga tushiradi; bunda chaqiruvchi uni useMemo/useCallback bilan barqaror ushlashi yoki hook ichida primitiv qismni dep qilishi kerak. Parametrlash — custom hookni bir marta yozib, ko'p holatda ishlatishning kaliti.

2.8. Hooklarni birlashtirish (composition — hook hookni chaqiradi)

text
  CUSTOM HOOK ichida boshqa CUSTOM HOOK chaqirilishi mumkin (kompozitsiya):

  function useDebouncedSearch(query) {
    const debouncedQuery = useDebounce(query, 400);   // custom hook ichida custom hook
    const { data, loading } = useFetch(                // yana custom hook
      debouncedQuery ? `/api/search?q=${debouncedQuery}` : null
    );
    return { results: data, loading };
  }
  // useDebouncedSearch = useDebounce + useFetch (ikkita kichik hook  bitta katta)

  ┌────────────────────────────────────────────────────────────┐
  │ Kichik, fokuslangan hooklar  birlashtirib murakkab mantiq   │
  │ (xuddi komponent kompozitsiyasi — 11.2: 2.9 — lekin mantiqda)│
  └────────────────────────────────────────────────────────────┘

   Kichik hooklar (useDebounce, useFetch) — qayta ishlatiladigan "g'ishtlar"
   Ularni birlashtirib (compose) yuqori darajadagi mantiq quriladi (toza, sinaladigan)

Hooklarni birlashtirish (composition) — custom hook ichida boshqa custom hook chaqirilishi mumkin, xuddi built-in hookni chaqirgandek. Masalan useDebouncedSearch — ichida useDebounce (qiymatni kechiktirish) va useFetch (ma'lumot olish) ni birlashtiradi: query debounced fetch natija. Ya'ni ikkita kichik, fokuslangan hook birlashib bitta yuqori darajadagi mantiqni tashkil qiladi. Bu — komponent kompozitsiyasiga (11.2: 2.9) o'xshaydi, lekin UI emas, mantiq darajasida. Bu yondashuvning kuchi: kichik, bitta ishni yaxshi qiladigan hooklar (useDebounce, useFetch, useToggle) — qayta ishlatiladigan "g'ishtlar"; ularni birlashtirib (compose) murakkabroq mantiq quriladi (useDebouncedSearch, useAuth, useInfiniteScroll). Har "g'isht" alohida sinaladi va boshqa joyda qayta ishlatiladi. Bu — toza, modulli React arxitekturasining cho'qqisi: UI komponentlar kompozitsiyasi + mantiq hooklar kompozitsiyasi.

2.9. useToggle va usePrevious — oddiy hooklar

text
  useToggle — boolean almashtirish (modal, accordion, switch):
  function useToggle(initial = false) {
    const [value, setValue] = useState(initial);
    const toggle = useCallback(() => setValue(v => !v), []);   // barqaror (11.6: 2.10)
    return [value, toggle];
  }

  usePrevious — qiymatning OLDINGI holatini eslab qolish (useRef — 11.5: 2.13):
  function usePrevious(value) {
    const ref = useRef();
    useEffect(() => { ref.current = value; }, [value]);   // har render'dan keyin joriy'ni sakla
    return ref.current;                                    // effektdan OLDIN o'qiladi  oldingi qiymat
  }
  // const prevCount = usePrevious(count);   // count o'zgarishidan oldingisi

   usePrevious — useRef'ning klassik ishlatilishi (render'siz "oldingi" qiymat — 11.5: 2.13)

useToggle va usePrevious — eng oddiy, lekin eng ko'p ishlatiladigan custom hooklar. useToggle — boolean qiymatni almashtirish (modal ochish/yopish, accordion, switch): ichida useState + toggle funksiya (updater v => !v — 11.4: 2.6, va useCallback bilan barqaror — 11.6: 2.10), [value, toggle] qaytaradi. usePrevious — qiymatning oldingi holatini eslab qolish (masalan animatsiya yoki "nima o'zgardi" uchun): useRef ishlatadi (11.5: 2.13) — useEffect har render'dan keyin joriy qiymatni ref'ga saqlaydi, lekin return ref.current effektdan oldin o'qiladi, shuning uchun oldingi (avvalgi render'dagi) qiymat qaytariladi. usePrevioususeRefning klassik namunasi (render'siz, ko'rinmaydigan "oldingi qiymat" saqlash). Bu ikki hook kichik bo'lsa-da, custom hook tuzilishining toza namunasi: bilgan built-in hookni (useState/useRef+useEffect) o'rab, qulay nom va interfeys berish.

2.10. useLocalStorage — state + saqlash (persistence)

text
  useLocalStorage — state localStorage'da SAQLANadi (sahifa qayta yuklansa — qoladi):

  function useLocalStorage(key, initialValue) {
    // 1. Boshlang'ich: localStorage'dan o'qi (lazy init — qimmat, bir marta — 11.5)
    const [value, setValue] = useState(() => {
      const saved = localStorage.getItem(key);
      return saved !== null ? JSON.parse(saved) : initialValue;
    });
    // 2. value o'zgarganda localStorage'ga yoz (sinxronlash — 11.5: 2.8)
    useEffect(() => {
      localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);
    return [value, setValue];                  // useState bilan bir xil interfeys
  }

  ISHLATISH (useState kabi, lekin saqlanadi):
  const [theme, setTheme] = useLocalStorage("theme", "light");
  // sahifani yangilab ochsang ham — theme saqlangan qoladi 

   useState + useEffect(persist) birlashmasi  "saqlanadigan state" (eng foydali hook)
   JSON.parse/stringify — obyekt/massivni ham saqlash uchun (string emas)

useLocalStorage — eng foydali custom hooklardan biri: state'ni localStorageda saqlaydi, shuning uchun sahifa qayta yuklansa ham qiymat qoladi (mavzu, til, savat, forma qoralamasi). Ikki qism: (1) boshlang'ich qiymatuseStatening lazy init 11.5-bob bilan localStoragedan o'qiladi (faqat birinchi render'da — qimmat o'qishni takrorlamaydi), agar yo'q bo'lsa initialValue; (2) saqlashuseEffect bilan value o'zgarganda localStoragega yoziladi (state'ni tashqi tizim bilan sinxronlash — 11.5: 2.8). JSON.parse/stringify — obyekt/massivni ham saqlash uchun (localStorage faqat string saqlaydi). Qaytarish interfeysi useState bilan bir xil ([value, setValue]) — shuning uchun useStateni useLocalStorage bilan to'g'ridan almashtirib bo'ladi: const [theme, setTheme] = useLocalStorage("theme", "light"). Bu hook — useState + useEffect(persist) birlashmasining klassik namunasi va custom hooklarning kuchini ko'rsatadi: oddiy funksiya, lekin butun loyihada "saqlanadigan state" muammosini hal qiladi. Yana bir nozik jihat: agar foydalanuvchi ikki tabda bir xil sahifani ochsa, bir tabdagi o'zgarish ikkinchisiga ko'chmaydi — buni qo'shish uchun brauzerning storage hodisasini tinglash mumkin (window.addEventListener("storage", ...) — boshqa tab localStorageni o'zgartirganda ishga tushadi). Bu — useEventListener (Misol 15) bilan qulay qo'shiladigan "tablararo sinxronizatsiya" imkoniyati; tayyor usehooks-ts versiyasida 2.14-bob allaqachon bor.

2.11. useFetch va useDebounce — effekt asosli hooklar

text
  useDebounce — qiymatni KECHIKTIRISH (tez o'zgarishda — oxirgisini kutish):
  function useDebounce(value, delay = 400) {
    const [debounced, setDebounced] = useState(value);
    useEffect(() => {
      const timer = setTimeout(() => setDebounced(value), delay);
      return () => clearTimeout(timer);        //  cleanup: yangi value kelsa eski timerni bekor qil (11.5: 2.7)
    }, [value, delay]);
    return debounced;                          // value to'xtagach yangilanadi
  }
  // const debouncedQuery = useDebounce(query, 400);  // har harfda emas, to'xtagach

  useFetch — ma'lumot olish (loading/error/data — qayta ishlatiladigan):
  function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(null);
    useEffect(() => {
      if (!url) return;                        // url yo'q bo'lsa — to'xta
      let ignore = false;                      // race himoyasi (11.5: 2.7)
      setLoading(true); setError(null);
      fetch(url)
        .then(r => { if (!r.ok) throw new Error("Xato"); return r.json(); })
        .then(d => { if (!ignore) setData(d); })
        .catch(e => { if (!ignore) setError(e.message); })
        .finally(() => { if (!ignore) setLoading(false); });
      return () => { ignore = true; };         // cleanup: eski javobni rad et
    }, [url]);
    return { data, loading, error };
  }

   Real loyihada useFetch o'rniga TanStack Query (cache, retry, dedup — 12.4) — lekin tushunish uchun shu

useDebounce va useFetchuseEffect asosli, eng amaliy custom hooklar. useDebounce — qiymatni kechiktirish: tez o'zgaradigan qiymatdan (har harf yozilganda) faqat oxirgisini olib, biroz kutib (400ms) qaytaradi — qidiruvda har harfda so'rov yubormaslik uchun ideal. Ichida setTimeout + cleanup (yangi qiymat kelsa eski taymerni bekor qiladi — 11.5: 2.7). useFetch — ma'lumot olishning qayta ishlatiladigan hook'i: data/loading/error state'larini boshqaradi, url o'zgarganda qayta yuklaydi, race condition'dan himoyalanadi (ignore bayrog'i — eski javob yangisini bosmasin — 11.5: 2.7), va obyekt qaytaradi. Bu ikkalasi — komponentdagi takrorlanuvchi mantiqni (debounce + fetch — har sahifada qayta yoziladigan) bir marta hookga ajratadi. Muhim eslatma: real loyihada useFetch o'rniga TanStack Query 12.4-bob ishlatiladi — u cache, qayta urinish (retry), so'rovlarni birlashtirish (dedup), fonda yangilash kabilarni avtomatik qiladi; lekin useFetchni qo'lda yozish — useEffect+fetch mexanizmini chuqur tushunish uchun bebaho.

2.12. DOM/event hooklari — useWindowSize, useOnClickOutside, useMediaQuery

text
  useWindowSize — oyna o'lchamini kuzatish (resize listener + cleanup):
  function useWindowSize() {
    const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
    useEffect(() => {
      const onResize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
      window.addEventListener("resize", onResize);
      return () => window.removeEventListener("resize", onResize);   // cleanup (11.5: 2.7)
    }, []);
    return size;
  }

  useOnClickOutside — element TASHQARISIGA bosishni aniqlash (modal/dropdown yopish):
  function useOnClickOutside(ref, handler) {
    useEffect(() => {
      const listener = (e) => {
        if (!ref.current || ref.current.contains(e.target)) return;  // ichida — e'tibormaY
        handler();                              // tashqarida — handler (yop)
      };
      document.addEventListener("mousedown", listener);
      return () => document.removeEventListener("mousedown", listener);
    }, [ref, handler]);
  }

  useMediaQuery — responsive shart (CSS media query JS'da):
  function useMediaQuery(query) {
    const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
    useEffect(() => {
      const mql = window.matchMedia(query);
      const onChange = () => setMatches(mql.matches);
      mql.addEventListener("change", onChange);
      return () => mql.removeEventListener("change", onChange);
    }, [query]);
    return matches;
  }
  // const isMobile = useMediaQuery("(max-width: 768px)");

   Bu hooklar — DOM/brauzer API'ni 11.1-bob React state'ga "ulaydi" (effekt+listener+cleanup naqshi)

DOM/event hooklari — brauzer API'sini React state'ga "ulaydigan" amaliy hooklar (hammasi useEffect + listener + cleanup naqshiga asoslanadi — 11.5: 2.7). useWindowSize — oyna o'lchamini kuzatadi (resize listener + cleanup), responsive layout uchun. useOnClickOutside — berilgan element tashqarisiga bosishni aniqlaydi (ref.current.contains(e.target) — bosilgan joy element ichidami?) — modal/dropdown'ni tashqariga bosganda yopish uchun klassik. useMediaQuery — CSS media query'ni JavaScript'da o'qiydi (window.matchMedia), responsive shart uchun (const isMobile = useMediaQuery("(max-width: 768px)")) — JSX'da shartli render qilish imkonini beradi. Bu hooklar 11.1-bobdagi DOM/event bilimini (querySelector, addEventListener, contains) React'ning deklarativ dunyosiga ulaydi: imperativ brauzer API'sini bir marta hookga o'rab, butun ilovada deklarativ tarzda ishlatasiz. Hammasida cleanup bor (listener'ni o'chirish — memory leak'dan saqlanish) — bu DOM hooklarining majburiy qoidasi.

2.13. Qachon custom hook ajratish (vs inline)

text
  CUSTOM HOOK AJRATISH BELGILARI (qachon ajratish kerak):
   BIR XIL stateful mantiq 2+ joyda takrorlansa (DRY — eng aniq belgi)
   Komponentda mantiq UI'dan KO'P bo'lib ketsa (komponent "to'lib" ketdi)
   Murakkab effekt/cleanup (debounce, subscription) — alohida sinash kerak
   Mantiqqa AYNIQSA YAXSHI nom bersa bo'lsa (useAuth, useCart — o'zini tushuntiradi)

  AJRATMA (inline qoldir):
   Bir marta ishlatiladigan oddiy mantiq (ortiqcha abstraksiya)
   Faqat hisob (hook emas — oddiy funksiya yetadi, "use" kerak emas — 2.2)
   "Balki kerak bo'lar" — oldindan ajratma (haqiqiy takror chiqsa ajrat)

  ┌────────────────────────────────────────────────────────────┐
  │ Takror ko'rinsa  ajrating. Bir marta  inline. (premature emas)│
  └────────────────────────────────────────────────────────────┘

   "Rule of three": bir xil mantiq 3-marta yozilsa — endi custom hook vaqti keldi
   Har hook aniq, bitta mas'uliyatli bo'lsin (9.1 SRP) — "xudo hook" yozma

Qachon custom hook ajratish — bu ham balans masalasi (11.6: 2.13 kabi). Ajratish belgilari: (1) bir xil stateful mantiq 2+ joyda takrorlansa (DRY — eng aniq belgi); (2) komponentda mantiq UI'dan ko'p bo'lib ketsa (komponentni "to'ldirsa" — toza qilish uchun ajratiladi); (3) murakkab effekt/cleanup (debounce, subscription) — alohida sinash kerak bo'lsa; (4) mantiqqa ayniqsa yaxshi nom berish mumkin bo'lsa (useAuth, useCart — o'zini tushuntiradi, niyatni bildiradi). Ajratmaslik kerak (inline qoldirish): bir marta ishlatiladigan oddiy mantiq (ortiqcha abstraksiya — kodni murakkablashtiradi); faqat hisob qiladigan narsa (bu oddiy funksiya, hook emas — 2.2); va "balki kerak bo'lar" deb oldindan ajratmaslik (haqiqiy takror chiqsa ajratiladi). Foydali qoida — "Rule of three": bir xil mantiqni uchinchi marta yozish paytida endi custom hook vaqti keladi. Va har hook aniq, bitta mas'uliyatli bo'lsin (9.1 SRP) — hamma narsani qiladigan "xudo hook" (useEverything) yozmaslik kerak; kichik, fokuslangan hooklar birlashtiriladi 2.8-bob. Avval oddiy yozib, takror sezilganda ajratiladi.

2.14. Sinash va tashqi kutubxonalar

text
  CUSTOM HOOK SINASH (test) — komponentdan MUSTAQIL (hook — sof mantiq):
  - @testing-library/react'ning renderHook() bilan 11.17-bob
  - hook chaqiriladi, qaytgan qiymat va o'zgarishlar tekshiriladi (komponentsiz)
   custom hook'ning afzalligi: UI'dan ajralgan mantiq  izolyatsiyalangan test

  TAYYOR KUTUBXONALAR (har narsani noldan yozma):
  - usehooks-ts — TypeScript bilan, 50+ tayyor hook (useLocalStorage, useDebounce...)
  - react-use — katta to'plam (useAsync, useToggle, useInterval...)
  - @uidotdev/usehooks — zamonaviy, sifatli to'plam
  - ahooks — korxona darajasidagi keng to'plam (Alibaba; useRequest kuchli)

  ┌────────────────────────────────────────────────────────────┐
  │ Oddiy/standart hooklar  tayyor kutubxonadan ol (sinalgan)   │
  │ Loyihaga XOS mantiq (useCart, useAuth)  o'zing yoz          │
  └────────────────────────────────────────────────────────────┘

   Tayyor hook — sinalgan, edge-case'lar hisobga olingan (SSR, cleanup)  ishonchli
   Lekin TUSHUNISH SHART: kutubxonani ishlatsa ham, ichida nima bo'layotganini bil (bu bob)

Sinash va tashqi kutubxonalar — custom hooklarning ikki amaliy jihati. Sinash: custom hook komponentdan mustaqil sinaladi (u — UI'dan ajralgan sof mantiq) — @testing-library/reactning renderHook() bilan hookni chaqirib, qaytgan qiymat va o'zgarishlarni komponentsiz tekshirasiz 11.17-bob. Bu — custom hookga ajratishning yana bir afzalligi: mantiq izolyatsiyalangan holda, oson sinaladi. Tashqi kutubxonalar: ko'p standart hook allaqachon yozilgan va sinalgan — har narsani noldan yozish shart emas: usehooks-ts (TypeScript, 50+ hook), react-use (katta to'plam), @uidotdev/usehooks (zamonaviy), ahooks (korxona darajasidagi keng to'plam, ayniqsa useRequest). Amaliy qoida: oddiy/standart hooklar (useLocalStorage, useDebounce, useMediaQuery) — tayyor kutubxonadan olinadi (ular sinalgan, SSR/cleanup kabi edge-case'lar hisobga olingan — ishonchliroq); loyihaga xos mantiq (useCart, useAuth, useCheckout) — o'zingiz yozasiz. Lekin tayyor kutubxona ishlatilganda ham, ichida nima bo'layotganini tushunish shart (aynan shu bob shu uchun) — uni qora quti sifatida emas, mexanizmini bilgan holda ishlatish debug va to'g'ri qo'llash uchun zarur.

2.15. Custom hook vs oddiy util funksiya (chegara aniq)

text
  SAVOL: mantiqni custom hook'ga qo'yaymi yoki oddiy util funksiyagami?

  BELGI — ichida React STATE yoki EFFEKT bormi (useState/useEffect/useRef/useContext...)?
   HA   custom hook kerak ("use" prefiksi, Rules of Hooks amal qiladi)
   YO'Q  oddiy util funksiya (import qilib istalgan joyda ishlat — hook emas)

  MISOL:
  formatDate(d)       sof hisob, state yo'q       util funksiya (utils/date.js)
  isValidEmail(s)     sof tekshiruv, state yo'q    util funksiya
  useLocalStorage()   useState + useEffect ichida  custom hook
  useDebounce()       useState + useEffect ichida  custom hook

  ┌────────────────────────────────────────────────────────────┐
  │ State/effekt kerak emas  util funksiya (oddiy, tez, tekin)  │
  │ State/effekt kerak       custom hook (React hayot siklida)  │
  └────────────────────────────────────────────────────────────┘

   "use" prefiksini FAQAT ichida hook bo'lsa qo'y — aks holda ESLint yolg'on ogohlantiradi

Custom hook vs oddiy util funksiya — amaliy chegara, ko'p yangi boshlovchi shu yerda adashadi (hamma narsani hook qilib yuboradi). Yagona mezon: funksiya ichida React state yoki effekt kerakmi? Agar mantiq shunchaki kirishdan chiqishni hisoblasa (sanani formatlash, email tekshirish, massivni saralash) — bu sof funksiya, unda hech qanday hook yo'q, demak u oddiy util funksiya (utils/ papkasida, use prefiksisiz, istalgan joyda — komponent, boshqa funksiya, hatto server'da — chaqiriladi). Agar mantiq useState/useEffect/useRef/useContext kabi hookni ishlatsa (holatni saqlash, tashqi tizimga ulanish, hayot sikliga bog'lanish) — bu custom hook (use prefiksi bilan, faqat komponent/hook ichida). Muhim: use prefiksini faqat ichida hook bo'lsa qo'ying — useFormatDate kabi nom (ichida hook yo'q) ESLint'ni chalg'itadi va Rules of Hooks tekshiruvini keraksiz qo'llaydi. Qoida sodda: state/effekt yo'q util; bor hook.

2.16. Custom hook va TypeScript (generic, tuple as const)

text
  TYPESCRIPT'DA CUSTOM HOOK — ikki asosiy naqsh (14-QISM'da chuqur):

  1) GENERIC <T> — hook turli tip bilan ishlaydi va tipni SAQLAYDI:
     function useLocalStorage<T>(key: string, initial: T): [T, (v: T) => void] { ... }
     const [n] = useLocalStorage("count", 0);   // n: number (avtomatik chiqariladi)

  2) MASSIV QAYTARSA — "as const" (aks holda tip keng/noaniq bo'ladi):
     return [value, toggle];            //  tip: (boolean | (() => void))[] — chalkash
     return [value, toggle] as const;   //  tip: [boolean, () => void] — aniq tuple

  ┌────────────────────────────────────────────────────────────┐
  │ Generic  hook qayta ishlatiluvchi VA tip-xavfsiz            │
  │ as const  massiv qaytarish uchun aniq tuple tipi (destruktr)│
  └────────────────────────────────────────────────────────────┘

   Obyekt qaytarsa "as const" shart emas (nomlar tipni saqlaydi) — massivda esa SHART

Custom hook va TypeScript — TypeScript loyihada (14-QISM) custom hook tip-xavfsiz bo'lishi uchun ikki naqsh bilinadi. (1) Generic <T>: hookni turli tip bilan ishlatish va tipni saqlab qolish uchun — useFetch<User>(url) yozilganda data avtomatik User tipini oladi, useLocalStorage<Theme> esa Themeni. Generic bo'lmasa hook any qaytaradi va tip xavfsizligi yo'qoladi. (2) as const tuple: hook massiv qaytarganda (return [value, toggle]), TypeScript uni keng tip — (boolean | (() => void))[] — deb chiqaradi, natijada destrukturda value ham funksiya, ham boolean bo'lishi mumkin ko'rinadi (tip chalkashadi). as const qo'shilsa (return [value, toggle] as const), TypeScript uni aniq tuple[boolean, () => void] — deb biladi va destruktur to'g'ri ishlaydi (xuddi useStatedek). Obyekt qaytarganda as const shart emas — obyekt nomlari tiplarni o'zi saqlaydi; muammo faqat massiv (tuple)da. Bu ikki naqsh — Misol 22'da; batafsili 14-QISM'da.


3. Sintaksis — tez ma'lumotnoma

text
TUZILISH 2.2-bob:   function useX(args) { /* useState/useEffect... */ return natija; }
QOIDA 2.3-bob:      nom "use" bilan; Rules of Hooks (tepada, shartsiz — 11.5: 2.2)
QAYTARISH 2.6-bob:  return [a, b]  (1-2, nomlanadigan)  |  return { a, b, c }  (3+, tanlab)
ARGUMENT 2.7-bob:   useFetch(url, { enabled }) ; effektda ishlatilsa deps'ga qo'sh
KOMPOZITSIYA2.8-bob:function useBig() { const x=useSmall1(); const y=useSmall2(); ... }
ULASHISH 2.4-bob:   har chaqiruv O'Z state'i; state ulashish kerak bo'lsa  Context 12.1-bob
HOOK vs UTIL2.15-bob:ichida hook bor  useX ; sof hisob  oddiy funksiya (util)
TYPESCRIPT2.16-bob: function useX<T>(...) ; massiv qaytsa "return [...] as const"
SINASH 2.14-bob:    renderHook(() => useX())  // komponentsiz 11.17-bob
KUTUBXONA:        usehooks-ts, react-use, @uidotdev/usehooks, ahooks

4. Batafsil kod namunalari

Misol 1 — useToggle (eng oddiy custom hook — 2.9)

jsx
import { useState, useCallback } from "react";

function useToggle(initial = false) {
  const [value, setValue] = useState(initial);
  const toggle = useCallback(() => setValue(v => !v), []);   // updater + barqaror (11.6)
  const setTrue = useCallback(() => setValue(true), []);
  const setFalse = useCallback(() => setValue(false), []);
  return { value, toggle, setTrue, setFalse };               // obyekt (3+ — 2.6)
}

// Ishlatish — modal:
function Modal() {
  const { value: isOpen, toggle, setFalse } = useToggle(false);
  return (
    <>
      <button onClick={toggle}>Modal ochish</button>
      {isOpen && (
        <div className="modal">
          <p>Modal kontenti</p>
          <button onClick={setFalse}>Yopish</button>
        </div>
      )}
    </>
  );
}

Misol 2 — useCounter (metodlar bilan)

jsx
function useCounter(initial = 0, { min = -Infinity, max = Infinity } = {}) {
  const [count, setCount] = useState(initial);
  const increment = useCallback(() => setCount(c => Math.min(c + 1, max)), [max]);
  const decrement = useCallback(() => setCount(c => Math.max(c - 1, min)), [min]);
  const reset = useCallback(() => setCount(initial), [initial]);
  return { count, increment, decrement, reset };
}

function Quantity() {
  const { count, increment, decrement } = useCounter(1, { min: 1, max: 10 });  // 1-10 oralig'ida
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

Misol 3 — usePrevious (useRef bilan — 2.9)

jsx
import { useRef, useEffect } from "react";

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;                       // har render'dan keyin joriy'ni sakla
  }, [value]);
  return ref.current;                          // return effektdan OLDIN  oldingi qiymat (11.5: 2.13)
}

function PriceTracker({ price }) {
  const prevPrice = usePrevious(price);
  const trend = prevPrice === undefined ? "" : price > prevPrice ? "" : price < prevPrice ? "" : "";
  return <p>Narx: {price} {trend} (oldin: {prevPrice ?? "—"})</p>;
}

Misol 4 — useLocalStorage (state + persistence — 2.10)

jsx
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    try {
      const saved = localStorage.getItem(key);
      return saved !== null ? JSON.parse(saved) : initialValue;   // lazy init (11.5)
    } catch {
      return initialValue;                     // JSON xato bo'lca — boshlang'ich
    }
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));   // value o'zgapganda saqla (11.5: 2.8)
  }, [key, value]);

  return [value, setValue];                    // useState bilan bir xil interfeys
}

// Ishlatish — saqlanadigan mavzu:
function App() {
  const [theme, setTheme] = useLocalStorage("theme", "light");
  return (
    <div className={theme}>
      <button onClick={() => setTheme(t => (t === "light" ? "dark" : "light"))}>
        Mavzu: {theme}
      </button>
    </div>
  );
}
//  Sahifani yangilab ochsang ham — theme saqlangan qoladi (2.10)

Misol 5 — useDebounce (qiymat kechiktirish — 2.11)

jsx
function useDebounce(value, delay = 400) {
  const [debounced, setDebounced] = useState(value);
  useEffect(() => {
    const timer = setTimeout(() => setDebounced(value), delay);
    return () => clearTimeout(timer);          //  yangi value kelsa — eski timerni bekor (11.5: 2.7)
  }, [value, delay]);
  return debounced;
}

function Search() {
  const [query, setQuery] = useState("");
  const debouncedQuery = useDebounce(query, 400);   // 400ms to'xtagach yangilanadi

  useEffect(() => {
    if (debouncedQuery) console.log("Qidirilmoqda:", debouncedQuery);  // bu yerda fetch
  }, [debouncedQuery]);                         // har harfda emas — to'xtagach

  return <input value={query} onChange={e => setQuery(e.target.value)} />;
}

Misol 6 — useFetch (ma'lumot olish — 2.11)

jsx
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    if (!url) return;                          // url bo'lmaca — yuklama
    let ignore = false;                        // race himoyasi (11.5: 2.7)
    setLoading(true); setError(null);
    fetch(url)
      .then(r => { if (!r.ok) throw new Error(`HTTP ${r.status}`); return r.json(); })
      .then(d => { if (!ignore) setData(d); })
      .catch(e => { if (!ignore) setError(e.message); })
      .finally(() => { if (!ignore) setLoading(false); });
    return () => { ignore = true; };           // cleanup: eski javobni rad et
  }, [url]);

  return { data, loading, error };
}

function UserList() {
  const { data: users, loading, error } = useFetch("/api/users");
  if (loading) return <Spinner />;
  if (error) return <p className="error">{error}</p>;
  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}
//  Real loyihada — TanStack Query 12.4-bob; bu hook mexanizmni tushunish uchun

Misol 7 — useWindowSize (resize listener — 2.12)

jsx
function useWindowSize() {
  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });

  useEffect(() => {
    const onResize = () => setSize({ width: window.innerWidth, height: window.innerHeight });
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);   // cleanup (11.5: 2.7)
  }, []);

  return size;
}

function Responsive() {
  const { width } = useWindowSize();
  return <p>{width < 768 ? " Mobil" : " Desktop"} ({width}px)</p>;
}

Misol 8 — useOnClickOutside (modal/dropdown yopish — 2.12)

jsx
function useOnClickOutside(ref, handler) {
  useEffect(() => {
    const listener = (e) => {
      if (!ref.current || ref.current.contains(e.target)) return;  // ichida bosildi — e'tibormaY
      handler();                               // tashqarida — handler (yop)
    };
    document.addEventListener("mousedown", listener);
    return () => document.removeEventListener("mousedown", listener);
  }, [ref, handler]);
}

function Dropdown() {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useOnClickOutside(ref, () => setOpen(false)); // tashqariga bosca yopiladi

  return (
    <div ref={ref} className="dropdown">
      <button onClick={() => setOpen(o => !o)}>Menyu</button>
      {open && <ul className="menu"><li>Profil</li><li>Chiqish</li></ul>}
    </div>
  );
}

Misol 9 — useMediaQuery (responsive shart — 2.12)

jsx
function useMediaQuery(query) {
  const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
  useEffect(() => {
    const mql = window.matchMedia(query);
    const onChange = () => setMatches(mql.matches);
    mql.addEventListener("change", onChange);
    return () => mql.removeEventListener("change", onChange);
  }, [query]);
  return matches;
}

function Layout() {
  const isMobile = useMediaQuery("(max-width: 768px)");
  return isMobile ? <MobileNav /> : <DesktopNav />;   // shartli komponent (11.4: 2.12)
}

Misol 10 — useInterval (Dan Abramov naqshi — ref bilan stale closure yechimi)

jsx
function useInterval(callback, delay) {
  const savedCallback = useRef(callback);

  useEffect(() => {
    savedCallback.current = callback;          // har render'da eng yangi callback'ni sakla
  }, [callback]);

  useEffect(() => {
    if (delay === null) return;                // null  to'xta (pauza)
    const id = setInterval(() => savedCallback.current(), delay);  // ref'dan eng yangini chaqir
    return () => clearInterval(id);            // cleanup
  }, [delay]);                                  // faqat delay o'zgapganda qayta o'rnat
}
//  Ref naqshi — stale closure'ni (11.5: 2.9) hal qiladi: callback yangilanadi,
//    lekin interval qayta o'rnatilmaydi (delay o'zgapmaca). Klassik custom hook namunasi.

function Timer() {
  const [count, setCount] = useState(0);
  const [running, setRunning] = useState(true);
  useInterval(() => setCount(c => c + 1), running ? 1000 : null);  // null  pauza
  return <button onClick={() => setRunning(r => !r)}>{count} {running ? "" : "▶"}</button>;
}

Misol 11 — Kompozitsiya: useDebouncedSearch (hook + hook — 2.8)

jsx
// Ikki kichik hookni birlashtirib (compose) yuqori darajadagi mantiq:
function useDebouncedSearch(query, delay = 400) {
  const debouncedQuery = useDebounce(query, delay);          // hook 1 (Misol 5)
  const { data, loading, error } = useFetch(                 // hook 2 (Misol 6)
    debouncedQuery ? `/api/search?q=${encodeURIComponent(debouncedQuery)}` : null
  );
  return { results: data ?? [], loading, error };
}

function SearchPage() {
  const [query, setQuery] = useState("");
  const { results, loading } = useDebouncedSearch(query);     // bitta toza hook

  return (
    <div>
      <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Qidirish..." />
      {loading && <small>Qidirilmoqda...</small>}
      <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
    </div>
  );
}
//  useDebounce + useFetch = useDebouncedSearch (mantiq kompozitsiyasi — 2.8)

Misol 12 — useForm (kichik forma hook'i)

jsx
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});

  const handleChange = useCallback((e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));          // dinamik maydon (11.4: 2.8)
  }, []);

  const reset = useCallback(() => { setValues(initialValues); setErrors({}); }, [initialValues]);

  return { values, errors, setErrors, handleChange, reset };
}

function ContactForm() {
  const { values, handleChange, reset } = useForm({ name: "", email: "" });
  return (
    <form onSubmit={(e) => { e.preventDefault(); console.log(values); reset(); }}>
      <input name="name" value={values.name} onChange={handleChange} />
      <input name="email" value={values.email} onChange={handleChange} />
      <button>Yuborish</button>
    </form>
  );
}
//  Bu — soddalashtirilgan forma hook'i; real loyihada React Hook Form 11.10-bob ancha kuchli

Misol 13 — State ulashilmasligini ko'rsatish (2.4)

jsx
// Bitta hook — ikki komponentda — IKKI ALOHIDA state (ulashilmaydi!)
function Counter({ label }) {
  const { count, increment } = useCounter(0);   // har Counter o'z count'i (Misol 2)
  return <button onClick={increment}>{label}: {count}</button>;
}

function App() {
  return (
    <>
      <Counter label="A" />     {/* count_A — mustaqil */}
      <Counter label="B" />     {/* count_B — mustaqil (A'ga aloqasi yo'q) */}
    </>
  );
}
//  A'ni bosca faqat A oshadi (B o'zgapmaydi) — hook MANTIQNI ulashadi, STATE'ni emas (2.4)
//    Agar A va B BIR XIL count ko'rsin desang  Context 12.1-bob yoki Redux (12.2)

Misol 14 — useAuth (loyihaga xos hook — Context bilan — 2.4, 2.8)

jsx
// Context + custom hook = global auth (state ULASHILadi — chunki Context — 2.4)
const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useLocalStorage("user", null);     // saqlanadigan (Misol 4)
  const login = useCallback(async (creds) => {
    const data = await api.login(creds);
    setUser(data.user);
  }, [setUser]);
  const logout = useCallback(() => setUser(null), [setUser]);
  const value = useMemo(() => ({ user, login, logout }), [user, login, logout]);  // barqaror (11.6: 2.9)
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

// Custom hook — Context'ni qulay o'rab beradi + xato tekshiruvi:
export function useAuth() {
  const ctx = useContext(AuthContext);
  if (!ctx) throw new Error("useAuth AuthProvider ichida ishlatilishi kerak");  // foydali xato
  return ctx;
}

// Istalgan joyda:
function Navbar() {
  const { user, logout } = useAuth();
  return user ? <button onClick={logout}>Chiqish</button> : <a href="/login">Kirish</a>;
}
//  useLocalStorage + Context + useMemo birlashmasi — real auth naqshi (2.8 kompozitsiya)

Misol 15 — useEventListener (universal listener hook — 2.12)

useWindowSize, useOnClickOutside va boshqa DOM hooklar ichida bir xil naqsh takrorlanadi: addEventListener cleanup'da removeEventListener. useEventListener — shu naqshni bitta joyga jamlaydigan "poydevor" hook (boshqa hooklarni ishlab chiqarish uchun ishlatiladi). Handler'ni useRef'da saqlash — useInterval'dagi (Misol 10) stale closure yechimi bilan bir xil: effekt handler o'zgarganda qayta o'rnatilmaydi, lekin har doim eng yangi handler'ni chaqiradi.

jsx
import { useRef, useEffect } from "react";

function useEventListener(eventName, handler, element = window) {
  const savedHandler = useRef(handler);

  useEffect(() => {
    savedHandler.current = handler;             // eng yangi handler'ni ref'da sakla (stale closure yo'q — Misol 10)
  }, [handler]);

  useEffect(() => {
    const target = element?.current ?? element; // ref bo'lsa .current, aks holda o'zi (window/document)
    if (!target?.addEventListener) return;
    const listener = (e) => savedHandler.current(e);   // ref orqali chaqir — doim yangi
    target.addEventListener(eventName, listener);
    return () => target.removeEventListener(eventName, listener);   // cleanup (11.5: 2.7)
  }, [eventName, element]);                      // handler deps'da EMAS — ref hal qildi
}

function ScrollTracker() {
  const [y, setY] = useState(0);
  useEventListener("scroll", () => setY(window.scrollY));   // handler har render yangi — muammo yo'q
  return <p>Scroll: {Math.round(y)}px</p>;
}
//  Poydevor hook: useWindowSize/useOnClickOutside'ni endi shu ustiga qurish mumkin (2.8)

Misol 16 — useThrottle (chastotani cheklash — debounce'ning "qardoshi")

useDebounce (Misol 5) tez o'zgarishning oxirgisini kutadi; useThrottle esa qiymatni muntazam oraliqda (masalan har 200ms'da bir marta) yangilaydi — to'xtashni kutmaydi. Scroll/resize/mousemove kabi uzluksiz hodisalar uchun ideal (debounce ular tinim olmasa hech qachon ishlamaydi). Farqni bilish muhim: debounce — "jim bo'lguncha kut", throttle — "belgilangan tezlikda o'tkaz".

jsx
function useThrottle(value, interval = 200) {
  const [throttled, setThrottled] = useState(value);
  const lastRun = useRef(Date.now());

  useEffect(() => {
    const sinceLast = Date.now() - lastRun.current;
    if (sinceLast >= interval) {
      lastRun.current = Date.now();
      setThrottled(value);                       // interval o'tgan — darrov yangila
    } else {
      const timer = setTimeout(() => {           // interval to'lguncha kut, keyin yangila
        lastRun.current = Date.now();
        setThrottled(value);
      }, interval - sinceLast);
      return () => clearTimeout(timer);          // cleanup (11.5: 2.7)
    }
  }, [value, interval]);

  return throttled;
}
// const throttledScroll = useThrottle(scrollY, 200);   // har 200ms'da eng ko'pi bir yangilanish
//  Debounce (oxirini kut) vs Throttle (muntazam o'tkaz) — ikkalasi ham "chastotani cheklash", turli holatga

Misol 17 — useCopyToClipboard (Clipboard API + holat)

jsx
function useCopyToClipboard() {
  const [copied, setCopied] = useState(false);

  const copy = useCallback(async (text) => {
    if (!navigator?.clipboard) {                 // eski brauzer — qo'llab-quvvatlanmaydi
      setCopied(false);
      return false;
    }
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);  // 2s'dan keyin "nusxalandi" holatini o'chir
      return true;
    } catch {
      setCopied(false);
      return false;
    }
  }, []);

  return { copied, copy };                        // 2+ qiymat  obyekt (2.6)
}

function ShareButton({ link }) {
  const { copied, copy } = useCopyToClipboard();
  return (
    <button onClick={() => copy(link)}>
      {copied ? " Nusxalandi" : " Havolani nusxalash"}
    </button>
  );
}
//  Async brauzer API'sini (Clipboard) + vaqtinchalik holatni (copied) qulay hookga o'raydi

Misol 18 — useIntersectionObserver (lazy load / infinite scroll)

IntersectionObserver — element ekranga (viewport'ga) kirdi-chiqdimi, buni samarali kuzatadigan brauzer API'si (scroll'ni qo'lda hisoblashdan tez). Ikki asosiy qo'llanish: lazy load (rasm ko'rinmaguncha yuklamaslik) va infinite scroll (ro'yxat oxiriga yetganda keyingi sahifani yuklash).

jsx
function useIntersectionObserver(options) {
  const [entry, setEntry] = useState(null);
  const ref = useRef(null);

  useEffect(() => {
    const node = ref.current;
    if (!node || !("IntersectionObserver" in window)) return;
    const observer = new IntersectionObserver(([e]) => setEntry(e), options);
    observer.observe(node);
    return () => observer.disconnect();          // cleanup — observer'ni uz (11.5: 2.7)
  }, [options]);                                  //  options barqaror bo'lsin (useMemo — 2.7)

  return [ref, entry];                            // [element'ga ulanadigan ref, kuzatuv natijasi]
}

// Infinite scroll: "sentinel" element ekranga kirsa — keyingi sahifa
function Feed({ loadMore }) {
  const [ref, entry] = useIntersectionObserver({ threshold: 1 });
  useEffect(() => {
    if (entry?.isIntersecting) loadMore();        // oxirdagi element ko'rindi  yukla
  }, [entry?.isIntersecting, loadMore]);
  return <div ref={ref} className="sentinel">Yuklanmoqda...</div>;
}
//  options obyekti chaqiruvchida useMemo bilan barqaror bo'lsin — aks holda effekt har render qayta ishlaydi (2.7, Xato 4)

Misol 19 — useAsync (ixtiyoriy async funksiyani boshqarish)

useFetch (Misol 6) faqat fetchga bog'langan. useAsync esa istalgan async funksiyani (login, fayl yuklash, hisob-kitob) idle/pending/success/error holatlari bilan boshqaradi — umumiylashtirilgan naqsh.

jsx
function useAsync(asyncFn) {
  const [state, setState] = useState({ status: "idle", data: null, error: null });

  const run = useCallback(async (...args) => {
    setState({ status: "pending", data: null, error: null });
    try {
      const data = await asyncFn(...args);
      setState({ status: "success", data, error: null });
      return data;
    } catch (error) {
      setState({ status: "error", data: null, error });
      throw error;
    }
  }, [asyncFn]);

  return { ...state, run, isLoading: state.status === "pending" };
}

function SaveButton({ save }) {
  const { run, isLoading, status } = useAsync(save);
  return (
    <button onClick={() => run()} disabled={isLoading}>
      {isLoading ? "Saqlanmoqda..." : status === "success" ? "Saqlandi " : "Saqlash"}
    </button>
  );
}
//  useFetch — "fetch"ga xos; useAsync — istalgan Promise'ga umumiy (login, upload, hisob)

Misol 20 — useDisclosure (ochiq/yopiq holat — modal/drawer uchun)

useToggle'ning "aniq niyatli" versiyasi: open/close/toggle — nom orqali o'zini tushuntiradi (modal, drawer, accordion boshqaruvi uchun standart naqsh; Chakra UI, Mantine kabi kutubxonalarda ham shu nom bor).

jsx
function useDisclosure(initial = false) {
  const [isOpen, setIsOpen] = useState(initial);
  const open = useCallback(() => setIsOpen(true), []);
  const close = useCallback(() => setIsOpen(false), []);
  const toggle = useCallback(() => setIsOpen((v) => !v), []);
  return { isOpen, open, close, toggle };
}

function Page() {
  const modal = useDisclosure();                  // bitta obyektda hammasi
  return (
    <>
      <button onClick={modal.open}>Ochish</button>
      {modal.isOpen && (
        <div className="modal">
          <p>Modal</p>
          <button onClick={modal.close}>Yopish</button>
        </div>
      )}
    </>
  );
}
//  Bitta obyekt qaytarish  ko'p modal boshqarilsa qulay: const create = useDisclosure(); const edit = useDisclosure();

Misol 21 — useFetch (kengaytirilgan: AbortController + refetch)

Misol 6'dagi useFetch — asos. Real ilovada yana ikki narsa kerak: (1) AbortController — komponent olib tashlanganda yoki url o'zgarganda haqiqiy tarmoq so'rovini bekor qilish (ignore bayrog'i faqat natijani e'tiborsiz qoldiradi, so'rov esa davom etadi — AbortController so'rovning o'zini to'xtatadi); (2) refetch — foydalanuvchi "qayta urinish" bosganda so'rovni qo'lda takrorlash.

jsx
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [reloadKey, setReloadKey] = useState(0);          // refetch uchun "trigger"

  const refetch = useCallback(() => setReloadKey((k) => k + 1), []);   // barqaror (2.6)

  useEffect(() => {
    if (!url) return;
    const controller = new AbortController();             //  so'rovni bekor qilish uchun
    setLoading(true);
    setError(null);
    fetch(url, { signal: controller.signal })
      .then((r) => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json();
      })
      .then((d) => setData(d))
      .catch((e) => {
        if (e.name !== "AbortError") setError(e.message);  // bekor qilish — xato emas, e'tiborsiz qoldir
      })
      .finally(() => {
        if (!controller.signal.aborted) setLoading(false);
      });
    return () => controller.abort();                       // cleanup: so'rovni HAQIQATAN to'xtat (11.5: 2.7)
  }, [url, reloadKey]);                                    // reloadKey o'zgarsa — qayta yuklaydi

  return { data, loading, error, refetch };                // refetch ham qaytariladi
}

function Products() {
  const { data, loading, error, refetch } = useFetch("/api/products");
  if (loading) return <Spinner />;
  if (error) return <button onClick={refetch}>Xato — qayta urinish</button>;   // refetch ishlatilishi
  return <ul>{data.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;
}
//  AbortController — real so'rovni to'xtatadi (ignore-bayroq faqat natijani tashlaydi); refetch — qo'lda qayta yuklash

Misol 22 — TypeScript bilan custom hook (generic + tuple as const — 14-bob)

TypeScript'da custom hook yozganda ikki muhim naqsh bor (14-QISM'da chuqur): (1) generic <T> — hook turli tiplar bilan ishlaydi va tipni saqlaydi (useFetch<User> data: User); (2) massiv qaytarganda as const — TypeScript uni aniq tuple deb biladi ([boolean, () => void]), aks holda (boolean | (() => void))[] deb keng, noaniq tip chiqaradi va destrukturda tiplar chalkashadi.

tsx
import { useState, useCallback, useEffect } from "react";

// Generic — qiymat tipini saqlaydi:
function useLocalStorage<T>(key: string, initialValue: T) {
  const [value, setValue] = useState<T>(() => {
    const saved = localStorage.getItem(key);
    return saved !== null ? (JSON.parse(saved) as T) : initialValue;
  });
  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);
  return [value, setValue] as const;             //  tuple: [T, Dispatch<SetStateAction<T>>]
}

// Ishlatish — tip avtomatik chiqariladi (inference):
const [count, setCount] = useLocalStorage("count", 0);        // count: number
const [user, setUser] = useLocalStorage<User>("user", null!); // user: User

// Toggle — aniq tuple tipi:
function useToggle(initial = false) {
  const [value, setValue] = useState(initial);
  const toggle = useCallback(() => setValue((v) => !v), []);
  return [value, toggle] as const;               // [boolean, () => void] — aniq
}
//  `as const` bo'lmasa — return tip (boolean | (() => void))[] bo'lib, destrukturda tiplar aralashadi (14-bob)

5. To'g'ri va noto'g'ri holatlar

1) Nomlash

text
 function getToggle() { useState(...) }  ("use" yo'q — ESLint hook deb ko'rmaydi — 2.3)
 function useToggle() { useState(...) }  ("use" prefiksi — majburiy)

2) Hook joylashuvi

text
 if (x) { const v = useFetch(url); }  (shart ichida — Rules buzildi — 2.3)
 const v = useFetch(x ? url : null); if (x) {...}  (tepada; shartni argument bilan)

3) State ulashish kutilmasi

text
 ikki komponentda useCart() — bir xil savat ko'rsin deb kutish (alohida state — 2.4)
 ulashiladigan state  Context/Redux; custom hook faqat mantiq (2.4)

4) Oddiy funksiyani hook qilish

text
 function useFormatDate(d) { return d.toLocaleString(); }  (hook yo'q ichida — 2.2)
 function formatDate(d) {...}  (oddiy funksiya — "use" kerak emas)

5) Argument deps

text
 function useFetch(url) { useEffect(() => fetch(url), []) }  (url deps'da yo'q — 2.7)
 }, [url])  (argument effektda  deps'ga qo'sh — 11.5: 2.6)

6) Oldindan ajratish

text
 har kichik mantiqni darrov hookga ajratish (ortiqcha abstraksiya — 2.13)
 takror (2+) yoki murakkablik chiqsa ajrat (Rule of three)

6. Keng tarqalgan xatolar va yechimlari

Xato 1 — React Hook "useState" is called in function "getX" which is neither a component nor a custom hook

Sababi: ichida hook bor funksiya use bilan boshlanmagan 2.3-bob. Yechimi: funksiyani use* deb nomla (getX useX).

Xato 2 — Custom hook'dagi state ikki komponentda "ulashilmayapti"

Sababi: custom hook state'ni emas, mantiqni ulashadi — har chaqiruv alohida 2.4-bob. Yechimi: ulashiladigan state uchun Context 12.1-bob yoki Redux/Zustand (12.2, 12.5).

Xato 3 — Rendered more hooks than during the previous render

Sababi: custom hookni shart/tsikl ichida chaqirding (2.3 — Rules of Hooks buzildi). Yechimi: hookni tepada, shartsiz chaqir; shartni hook argumentiga yoki ichiga ko'chir.

Xato 4 — useFetch/useEffect cheksiz qayta ishlaydi

Sababi: argument (url/options) har render yangi havola (obyekt — 11.6: 2.9), yoki deps noto'g'ri. Yechimi: primitiv argument; obyekt argumentni useMemo bilan barqaror ush 2.7-bob.

Xato 5 — Stale closure (interval/listener eski qiymat ko'radi)

Sababi: effekt [] bilan, ichida eski state/callback (11.5: 2.9). Yechimi: updater funksiya, yoki useRef naqshi (Misol 10 — useInterval).

Xato 6 — localStorage hook SSR'da (Next.js) xato beradi (localStorage is not defined)

Sababi: server'da window/localStorage yo'q (13-QISM). Yechimi: typeof window !== "undefined" tekshir, yoki tayyor SSR-xavfsiz hook (usehooks-ts — 2.14).

Xato 7 — useAuth must be used within AuthProvider

Sababi: Context hook'i Provider tashqarisida ishlatildi (2.4, Misol 14). Yechimi: ildizda <AuthProvider> bilan o'ra; hook'da aniq xato tashla (Misol 14).


7. Integratsiya — bu mavzu stack'ning qayerida uchraydi

  • Barcha hooklar (11.5, 11.6): custom hook ularni birlashtiradi (useState/useEffect/useRef/useReducer).
  • Komponent kompozitsiyasi (11.2: 2.9): custom hook — mantiq kompozitsiyasi (UI emas).
  • Context 12.1-bob: state ulashish — custom hook bilan birga (useAuth — Misol 14).
  • TanStack Query 12.4-bob: useFetch'ning professional, cache'li muqobili.
  • React Hook Form 11.10-bob: useForm'ning kuchli versiyasi.
  • Testing 11.17-bob: renderHook bilan custom hook sinash (komponentsiz, izolyatsiyalangan).
  • TypeScript (14-QISM): generic <T> va as const tuple bilan tip-xavfsiz custom hook (2.16, Misol 22).
  • Performance 11.11-bob: hook ichida useMemo/useCallback (barqaror qaytarish).
  • Next.js (13): SSR-xavfsiz hooklar (window tekshiruvi — Xato 6).

8. Eng yaxshi amaliyotlar (best practices)

  • use prefiksi majburiy (ESLint hookni tanisin — 2.3, Xato 1).
  • Bir hook — bitta mas'uliyat (SRP; "xudo hook" yozma — 2.13, 9.1).
  • Rule of three (takrorni sezganda ajrat; oldindan emas — 2.13).
  • Logika ulashiladi, state emas (ulashish Context/Redux — 2.4).
  • Qaytarishda konvensiya (1-2 massiv; 3+ obyekt — 2.6).
  • Argumentlar deps'da (exhaustive-deps — effekt to'g'riligi — 2.7, 11.5: 2.6).
  • Cleanup har band qiluvchi effektda (listener/timer/subscription — 2.12, 11.5: 2.7).
  • Barqaror qaytarish (funksiyalarni useCallback, obyektlarni useMemo — chaqiruvchi effekt deps uchun — 11.6).
  • Kichik hooklarni birlashtir (composition > katta monolit hook — 2.8).
  • Standart hooklarni kutubxonadan ol (usehooks-ts; loyihaga xos — o'zingiz yoz — 2.14).

9. Amaliy loyiha: "O'z Hooklar Kutubxonang (Hooks Library)"

O'zingning qayta ishlatiladigan custom hooklar to'plamini yarat va ularni real ilovada ishlat.

Maqsad

8-10 ta amaliy custom hook yoz (alohida fayllarda), ularni hujjatla, va kichik ilovada (masalan blog yoki do'kon) birga ishlat.

Talablar (requirements)

  1. useToggle — boolean almashtirish (Misol 1).
  2. useLocalStorage — saqlanadigan state, lazy init + persist (Misol 4).
  3. useDebounce — qiymat kechiktirish, cleanup bilan (Misol 5).
  4. useFetch — ma'lumot olish, loading/error/data + race himoyasi (Misol 6).
  5. useWindowSize yoki useMediaQuery — responsive (Misol 7, 9).
  6. useOnClickOutside — modal/dropdown yopish (Misol 8).
  7. useInterval — ref naqshi bilan, stale closure'siz (Misol 10).
  8. Kompozitsiya: kamida bitta hook boshqa hook(lar)ni birlashtirsin (useDebouncedSearch — Misol 11, 2.8).
  9. To'g'ri qaytarish: 1-2 qiymat massiv, 3+ obyekt 2.6-bob; funksiyalar useCallback bilan barqaror.
  10. Real ishlatish: bu hooklarni kichik ilovada ishlat (qidiruv + saqlanadigan sozlama + responsive + modal).

Maslahatlar (hint)

  • Har hookni alohida faylda (hooks/useToggle.js) — hooks/ papkasi (11.3: 2.9).
  • Cleanup'ni unutma (listener/timer — Misol 5, 7, 8, 10).
  • use prefiksisiz nomlama (ESLint ogohlantiradi — 2.3).
  • State ulashish kerak bo'lsa (savat) — custom hook emas, Context (Misol 14, 2.4).
  • Argument effektda ishlatilsa — deps'ga qo'sh (Misol 6, 2.7).
  • useIntervalda ref naqshini ishlat (stale closure'dan saqlanish — Misol 10).
  • Tugagach: o'z hooklaringni usehooks-ts bilan solishtir — qanday qilingan 2.14-bob.

"Tayyor" mezonlari (acceptance criteria)

  • 8-10 custom hook, har biri alohida faylda, use prefiksi bilan.
  • Har hook bitta aniq mas'uliyatli (SRP).
  • Cleanup kerakli hooklarda mavjud (memory leak yo'q).
  • Kamida bitta kompozitsiya (hook ichida hook).
  • To'g'ri qaytarish konvensiyasi (massiv/obyekt).
  • Qaytarilgan funksiyalar barqaror (useCallback).
  • Hooklar real ilovada ishlatilgan.
  • State ulashish kerak joyda Context (custom hook emas) ishlatilgan.

Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.


10. Xulosa va keyingi bobga ko'prik

Bu bobda custom hooklar — mantiqni qayta ishlatishning React usulini chuqur o'rgandik:

  • Mantiq ulashish muammosi (HOC/render props custom hook — 2.1); custom hook nima (use* funksiya — 2.2); qoidalar (use prefiksi, Rules of Hooks — 2.3).
  • Eng muhim — logikani ulashadi, state'ni emas (har chaqiruv izolyatsiyalangan — 2.4); ajratish jarayoni 2.5-bob; qaytarish (massiv/obyekt — 2.6); parametrlash 2.7-bob; kompozitsiya 2.8-bob.
  • Amaliy hooklar: useToggle/useCounter/usePrevious (2.9, Misol 1-3), useLocalStorage (2.10, Misol 4), useFetch/useDebounce/useThrottle (2.11, Misol 5-6, 16, 21 — AbortController+refetch bilan), DOM hooklar useWindowSize/useOnClickOutside/useMediaQuery/useEventListener/useIntersectionObserver (2.12, Misol 7-9, 15, 18), va useCopyToClipboard/useAsync/useDisclosure/useForm/useAuth (Misol 17, 19-20, 12, 14); hook vs util funksiya 2.15-bob; TypeScript bilan (2.16, Misol 22); qachon ajratish 2.13-bob; sinash va kutubxonalar 2.14-bob.

Endi siz mantiqni toza, qayta ishlatiladigan hooklarga ajratib, butun loyiha bo'ylab DRY va modulli kod yoza olasiz. Bu — siz 11.5-11.6'da o'rgangan barcha hooklarni amalda birlashtiradigan, eng kuchli React ko'nikmalaridan biri.

Keyingi bob — 11.8-bob: React.lazy, Suspense va code splitting. Ilovamiz o'sgani sayin, butun kodni bir vaqtda yuklash sekinlashadi. Code splitting — kodni bo'laklarga bo'lib, faqat kerak bo'lganda yuklash. React.lazy komponentni "dangasa" (kerak bo'lganda) yuklaydi, Suspense esa yuklanayotgan paytda zaxira UI (spinner) ko'rsatadi. Bu — katta ilovalarning yuklanish tezligini (performance) keskin oshiradigan muhim texnika (11.11, 13.10 bilan bog'liq).


Foydalanilgan rasmiy/ishonchli manbalar

  • React rasmiy hujjati (react.dev):
    • "Reusing Logic with Custom Hooks" — custom hook nima, use prefiksi, logika ulashish (state emas), ajratish jarayoni
    • "Removing Effect Dependencies", "Lifecycle of Reactive Effects", "You Might Not Need an Effect" — effekt asosli hooklar (useFetch, useDebounce) va deps to'g'riligi
    • "Rules of Hooks" — custom hook ichida va tashqarisida hook chaqirish qoidalari
  • Dan Abramov — "Making setInterval Declarative with React Hooks" (useInterval ref naqshi, stale closure yechimi — Misol 10)
  • Tayyor custom hook to'plamlari (2026):
    • usehooks-ts (usehooks-ts.com) — TypeScript, SSR-xavfsiz, storage event sinxronizatsiyasi bilan
    • @uidotdev/usehooks, react-use, ahooks (Alibaba — useRequest)
  • TanStack Query (tanstack.com/query) — useFetchning professional muqobili (cache, retry, dedup — 12.4)
  • @testing-library/react — renderHook bilan custom hook sinash (11.17)
  • MDN Web Docs:
    • localStorage, Storage: storage event (tablararo sinxronizatsiya)
    • matchMedia (useMediaQuery), AbortController/AbortSignal (useFetch bekor qilish)
    • IntersectionObserver (useIntersectionObserver — lazy load/infinite scroll)
    • Clipboard API: writeText (useCopyToClipboard), Node.contains (useOnClickOutside)

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
11.7-bob: Custom hooks (o'z hooklaringni yozish) — Wisar