Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. use(promise) qanday ishlaydi
- 2.1 Uch holat
- 2.2 React Promise'ga belgi qo'yadi
- 2.3 Promise hodisada tug'iladi
- 3. Tuzoq: Promise render ichida
- 3.1 Cheksiz kutish
- 3.2 Nega halqa
- 3.3 React'ning ogohlantirishi
- 4. Kesh: bitta kalit — bitta Promise
- 4.1 Map bilan kesh
- 4.2 Keshning narxi
- 4.3 WeakMap: kalit — obyektning o'zi
- 5. Rad etilgan Promise
- 5.1 use xatoni tashlaydi
- 5.2 Xatoni qiymatga aylantirish
- 5.3 try/catch ichida use — yo'q
- 6. use(Context)
- 6.1 Shart, sikl va erta return dan keyin
- 6.2 Faqat render paytida
- 6.3 Qoidalar jadvali
- 7. Serverdan uzatilgan Promise
- 8. Vazifalar qadami: batafsil statistika (2-qism)
- 8.1 statistika-vadasi.ts — kesh
- 8.2 Test va topilgan tuzoq
- 8.3 Commit va tekshiruvlar
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): mumkinmi?
- 2-mashq (o'rta): talonni cho'ntakka soling
- 3-mashq (qiyin): tugma bilan yuklash va xato
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
use(): Promise va Context'ni o'qish — kesh, shartli chaqiruv va tuzoqlar
Qisqacha:
use(promise)Promise tayyor bo'lsa qiymatini qaytaradi. Tayyor bo'lmasa — komponentni to'xtatadi va eng yaqin<Suspense>fallback'ini ko'rsatadi. Rad etilgan Promise'da esa xato tashlaydi. Asosiy qoida: Promise render ichida yaratilmaydi. U modul darajasida, hodisa ishlovchisida yoki keshda yaratiladi va bir xil so'rov uchun o'sha Promise qaytadi. Aks holda har render yangi Promise beradi va kutish abadiy davom etadi.use(Context)esauseContextning o'zi, faqatifva sikl ichida ham chaqirsa bo'ladi. Ikkala holatda hamusefaqat render paytida ishlaydi: hodisa ishlovchisida vatry/catchichida — yo'q.
Bu darsda
use(promise)ning uch holatini (kutish, qiymat, xato) jonli ko'rasiz va React Promise'ga qanday belgi qo'yishini konsolda kuzatasiz.- "Promise render ichida" tuzog'ini ko'rasiz: cheksiz kutish va React'ning haqiqiy ogohlantirishi.
- Promise'ni to'g'ri joyda yaratasiz: hodisada, state'da yoki
Map/WeakMapkeshida. - Rad etilgan Promise'ni
.catchbilan qiymatga aylantirasiz va negausenitry/catchga o'rab bo'lmasligini tushuntirasiz. use(Context)ni shart va sikl ichida ishlatasiz vavazifalardagi statistika qadamini commit qilasiz.
Oldin bilishingiz kerak: Suspense, Context I, Hooklar qoidalari, Promise asoslari.
1. Nega bu kerak?
Oldingi darsda hamma Promise modulning boshida, bir marta yaratildi: const menuPromise = new Promise(…). Haqiqiy ilovada esa so'rov nimagadir bog'liq: qaysi taom tanlangan, qaysi tab ochilgan, foydalanuvchi qachon tugmani bosgan.
Sardor taom sahifasini yozdi va eng tabiiy ko'ringan yo'lni tanladi:
function Dish({ id }) {
const dish = use(fetchDish(id)); // ❌
return <p>{dish.nom}</p>;
}Sahifa ochildi — va abadiy "Yuklanmoqda…" da qoldi. Server javob berdi, konsolda xato yo'q, lekin taom chiqmadi. Bu darsda nega shunday bo'lganini va qanday tuzatilishini ko'ramiz.
Hayotiy o'xshatish: davlat xizmatlari markazidagi navbat taloni. Kirganda talon olasiz va tablo sizning raqamingizni chaqirishini kutasiz. Endi tasavvur qiling: tabloga har qaraganingizda yangi talon olasiz. Raqamingiz hech qachon chaqirilmaydi — har safar navbat oxiridasiz. use(fetchDish(id)) — aynan shu. To'g'ri yo'l — talonni bir marta olib, cho'ntakda saqlash. Kodda bu cho'ntakning nomi — kesh.
2. use(promise) qanday ishlaydi
2.1 Uch holat
Promise asoslari darsidan bilasiz: Promise uch holatdan birida bo'ladi. use har biriga o'zicha javob beradi:
| Promise holati | use(promise) |
Ekranda |
|---|---|---|
| pending (kutilmoqda) | komponentni to'xtatadi | eng yaqin <Suspense> fallback'i |
| fulfilled (bajarildi) | qiymatni qaytaradi | kontent |
| rejected (rad etildi) | xatoni tashlaydi | xato qutisi yoki error boundary |
Uchinchi qatorga «Rad etilgan Promise» bo'limida qaytamiz. Avval ikkinchisini chuqurroq ko'ramiz.
2.2 React Promise'ga belgi qo'yadi
Promise'ning holatini JavaScript'da sinxron bilib bo'lmaydi: faqat .then orqali, keyinroq. Unda React tayyor Promise'ni qayta-qayta kutmaslikni qanday biladi? U Promise obyektining o'ziga ikki maydon yozib qo'yadi: status va value. Buni konsolda ko'ramiz. Promise 100 ms da tayyor bo'ladi, tugma esa 300 ms da bosiladi:
import { Suspense, use, useEffect, useState } from "react";
const pricePromise = new Promise((resolve) => {
setTimeout(() => resolve(35000), 100);
});
function Price() {
console.log("oldin:", pricePromise.status);
const price = use(pricePromise);
console.log("keyin:", pricePromise.status, pricePromise.value);
return <p>Osh: {price} so'm</p>;
}
function Loading() {
useEffect(() => console.log("fallback ekranga chiqdi"), []);
return <p>Yuklanmoqda…</p>;
}
export default function App() {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setShow(true)}>Ko'rsatish</button>
<Suspense fallback={<Loading />}>{show && <Price />}</Suspense>
</div>
);
}Konsolda:
oldin: undefined
fallback ekranga chiqdi
oldin: fulfilled
keyin: fulfilled 35000Ekranda:
Ko'rsatish
Osh: 35000 so'mIkki narsa ko'rinadi:
- Birinchi duch kelganda Promise allaqachon tayyor edi, lekin
status—undefined. React buni bilmagan, shuning uchun komponent baribir bir lahza to'xtadi va fallback chiqdi. React.thenqo'shdi, javob kelgachstatus: "fulfilled"vavalue: 35000ni yozdi. - Keyingi renderlarda
usemaydonga qaraydi va qiymatni darhol, to'xtamasdan qaytaradi.
Xulosa: o'sha Promise obyekti ikkinchi marta kelsa — qiymat darhol. Yangi obyekt esa har safar birinchi martadek: belgisi yo'q. Butun dars shu ikki gap atrofida.
Maslahat:
statusvavalue— React'ning ichki kelishuvi, o'z kodingizda ularga tayanmang. Biz ularni faqat mexanizmni ko'rish uchun o'qidik.
2.3 Promise hodisada tug'iladi
Eng toza yo'l — Promise'ni hodisa ishlovchisida yaratish va state'da saqlash. Hodisa bir marta ishlaydi — Promise ham bitta. Komponent esa uni props orqali olib, use bilan o'qiydi:
import { Suspense, use, useState } from "react";
function fetchPrices() {
console.log("so'rov yuborildi");
return new Promise((resolve) => {
setTimeout(() => resolve({ osh: 35000, manti: 30000 }), 300);
});
}
function Prices({ pricesPromise }) {
const prices = use(pricesPromise);
return (
<p>
Osh: {prices.osh} · Manti: {prices.manti}
</p>
);
}
export default function App() {
const [pricesPromise, setPricesPromise] = useState(null);
function handleClick() {
setPricesPromise(fetchPrices()); // ✅ Promise — hodisada
}
return (
<div>
<button onClick={handleClick}>Narxlarni yuklash</button>
<Suspense fallback={<p>Narxlar yuklanmoqda…</p>}>
{pricesPromise && <Prices pricesPromise={pricesPromise} />}
</Suspense>
</div>
);
}Konsolda:
so'rov yuborildiEkranda (bosgandan 100 ms keyin):
Narxlarni yuklash
Narxlar yuklanmoqda…O'sha kod, 500 ms kutib:
// yuqoridagi kod — o'zgarishsizEkranda (bosgandan 500 ms keyin):
Narxlarni yuklash
Osh: 35000 · Manti: 30000So'rov bir marta yuborildi, garchi Prices bir necha marta chizilgan bo'lsa ham. Promise state'da yashaydi: har render state'dan o'sha obyektni oladi. Tugmani qayta bossangiz — yangi so'rov, yangi Promise. Bu to'g'ri: foydalanuvchi yangilashni o'zi so'radi.
Tekshirib ko'ring: Nega
fetchPrices()niuseState(fetchPrices())deb boshlang'ich qiymatga yozish yomon fikr?
Javob
useState(fetchPrices()) da fetchPrices() har renderda chaqiriladi — React faqat birinchi natijani saqlaydi, qolganlarini tashlaydi. Har render — yangi so'rov, keraksiz tarmoq yuki. To'g'risi — dangasa boshlash: useState(() => fetchPrices()) (State I). Lekin komponent to'xtab, hali ekranga chiqmagan bo'lsa, React uning state'ini saqlamaydi. Keyingi urinishda boshlang'ich funksiya yana chaqiriladi. Shuning uchun Promise'ni to'xtaydigan komponentning tashqarisida saqlang.
3. Tuzoq: Promise render ichida
3.1 Cheksiz kutish
Endi Sardorning xatosini jonli ko'ramiz. fetchMenu har chaqirilganda yangi Promise yaratadi. Konsolni toza saqlash uchun faqat birinchi uchtasini va o'ninchisini yozamiz:
import { Suspense, use } from "react";
let count = 0;
function fetchMenu() {
count++;
if (count <= 3 || count === 10) console.log(`Promise #${count}`);
return new Promise((resolve) => {
setTimeout(() => resolve(["Osh", "Manti"]), 100);
});
}
function Menu() {
const dishes = use(fetchMenu()); // ❌ har renderda yangi Promise
return <p>{dishes.join(", ")}</p>;
}
export default function App() {
return (
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Menu />
</Suspense>
);
}Konsolda (2,5 soniyadan keyin):
Promise #1
Promise #2
Promise #3
Promise #10Ekranda:
Yuklanmoqda…Har Promise 100 ms da hal bo'ladi, lekin menyu 2,5 soniyada ham chiqmadi. «Ishga tushir» natija oynasida u umuman chiqmaydi — oynani yopguncha yangi Promise'lar yaratilaveradi.
3.2 Nega halqa
Diagrammada halqaga qarang. Har aylanishda use qo'liga yangi, belgisi yo'q Promise tushadi:
flowchart TD
A["React Menu ni chizadi"] --> B["fetchMenu(): YANGI Promise"]
B --> C{"Promise'da belgi bormi?"}
C -- "yo'q, hech qachon" --> D["Menu to'xtaydi, fallback"]
D --> E["Promise hal bo'ldi"]
E --> A
C -. "kesh bo'lsa: ha" .-> F["Qiymat qaytadi"]- Render:
fetchMenu()→ Promise #1,useuni bilmaydi → to'xtash. - 100 ms: Promise #1 hal bo'ldi. React
Menuni boshidan qayta chizadi. - Qayta render:
fetchMenu()yana chaqirildi → Promise #2. U ham yangi, ham tayyor emas → yana to'xtash.
React "bu o'sha so'rov" deb taxmin qila olmaydi. Uning uchun ikki Promise — ikki xil obyekt, xuddi ikki talon kabi. Komponentni React istagan paytda qayta chaqiradi: to'xtagandan keyin, prewarm'da, StrictMode'da (Pure komponentlar). Shuning uchun render ichidagi har new Promise — yangi talon.
3.3 React'ning ogohlantirishi
Promise juda tez hal bo'lsa (masalan, Promise.resolve), React to'xtagan komponentni o'sha urinish ichida darhol qayta sinaydi. Shunda u Promise almashganini sezadi:
import { Suspense, use } from "react";
let count = 0;
function fetchMenu() {
count++;
console.log("yangi Promise #" + count);
return Promise.resolve(["Osh", "Manti"]); // darhol tayyor
}
function Menu() {
const dishes = use(fetchMenu()); // ❌ baribir har renderda yangi
return <p>{dishes.join(", ")}</p>;
}
export default function App() {
return (
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Menu />
</Suspense>
);
}Konsolda:
yangi Promise #1
yangi Promise #2
yangi Promise #3
A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework.Ekranda:
Osh, MantiTarjimasi: "Komponent keshlanmagan Promise tufayli to'xtadi. Client komponent yoki hook ichida Promise yaratish hozircha qo'llab-quvvatlanmaydi — faqat Suspense bilan ishlaydigan kutubxona yoki framework orqali." Bu yerda menyu chiqdi — React yangi Promise o'rniga eskisining qiymatini oldi. Lekin bu omad. Haqiqiy so'rov (100 ms) bo'lsa — oldingi bo'limdagi cheksiz halqa, ba'zan esa ogohlantirishsiz.
vazifalar kanonini qurishda xuddi shu xabar Chrome'da ham chiqdi. Statistika Promise'i vaqtincha keshsiz qilinib, npm run dev (StrictMode) da ochildi. Konsolda aynan o'sha matn chiqdi, panel esa baribir ochildi — hisob tez (setTimeout 0). Ogohlantirish — kod xato ekanining belgisi, natija to'g'ri chiqqan bo'lsa ham.
Tekshirib ko'ring: "Ekranda natija to'g'ri, faqat konsolda ogohlantirish — e'tibor bermasa ham bo'ladi" degan fikr nega xavfli?
Javob
Natija tezlikka bog'liq. Bugun hisob 1 ms da tugaydi va React eski Promise qiymatini olib ulguradi. Ertaga ma'lumot serverdan kelsa yoki ro'yxat kattalashsa — halqa paydo bo'ladi, sahifa abadiy "Yuklanmoqda…" da qoladi. Ogohlantirish — kelajakdagi xatoni bugun ko'rsatyapti.
4. Kesh: bitta kalit — bitta Promise
4.1 Map bilan kesh
Talonni cho'ntakda saqlaymiz. Kesh — kalit bo'yicha Promise'lar ombori: id birinchi marta so'ralganda Promise yaratiladi va saqlanadi. Keyingi safar — o'sha obyekt qaytadi:
import { Suspense, use, useState } from "react";
const DISHES = { 1: "Osh — 35000 so'm", 4: "Manti — 30000 so'm" };
const cache = new Map();
function fetchDish(id) {
if (!cache.has(id)) {
console.log("so'rov: taom", id);
const promise = new Promise((resolve) => {
setTimeout(() => resolve(DISHES[id]), 200);
});
cache.set(id, promise);
}
return cache.get(id);
}
function Dish({ id }) {
const dish = use(fetchDish(id)); // ✅ bitta id — bitta Promise
return <p>{dish}</p>;
}
export default function App() {
const [id, setId] = useState(1);
return (
<div>
<button onClick={() => setId(1)}>Osh</button>{" "}
<button onClick={() => setId(4)}>Manti</button>
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Dish id={id} />
</Suspense>
</div>
);
}Konsolda:
so'rov: taom 1
so'rov: taom 4Ekranda (oshga qaytgandan 100 ms keyin):
Osh Manti
Osh — 35000 so'mOsh → manti → yana osh, lekin so'rov faqat ikkita. Oshga qaytganda fallback ham chiqmadi. Keshdagi Promise'da React'ning belgisi bor (fulfilled), shuning uchun use qiymatni darhol berdi. fetchDish(id) endi render ichida chaqirilsa ham xavfsiz: u Promise yaratmaydi, keshdan oladi.
| Bosish | Keshda | So'rov | Fallback |
|---|---|---|---|
ochilish (id 1) |
1 yo'q → yaratildi | ha | ha |
«Manti» (id 4) |
4 yo'q → yaratildi | ha | ha |
«Osh» (id 1) |
1 bor, fulfilled |
yo'q | yo'q |
4.2 Keshning narxi
Kesh bepul emas. Uch savol paydo bo'ladi:
- Qachon eskiradi? Oshning narxi o'zgarsa, keshdagi Promise eski narxni abadiy beradi. Uni qachondir o'chirish (invalidatsiya) kerak.
- Xotira.
Maphar kalitni sahifa yopilguncha ushlab turadi. Minglab kalit — minglab Promise. - Xato ham keshlanadi. Rad etilgan Promise keshda qolsa, "Qayta urinish" ham o'sha xatoni qaytaradi — uni keshdan olib tashlash kerak.
Bularni qo'lda yechish — butun bir soha. Shuning uchun real ilovada kesh kutubxonaga topshiriladi: TanStack Query (TanStack Query II: kesh hayot sikli) yoki framework. Ular ichida aynan bugungi g'oya bor: "bir kalit — bitta Promise". Bugungi Map — o'rganish va kichik holatlar uchun.
React'ning o'zida cache() funksiyasi ham bor, lekin u faqat server komponentlari uchun (React cache()). Brauzer kodida u keshlamaydi.
4.3 WeakMap: kalit — obyektning o'zi
Ba'zan kalit — raqam emas, obyekt: masalan, butun vazifalar massivi. Bunda WeakMap qulay. Obyekt boshqa hech qayerda ishlatilmay qolsa, uning yozuvi keshdan o'zi yo'qoladi. Bu vazifalar dagi statistika keshining asosi — «Vazifalar qadami» bo'limida ko'ramiz.
5. Rad etilgan Promise
5.1 use xatoni tashlaydi
Server 500 qaytardi — Promise rad etildi. use uni render ichida tashlaydi, xuddi komponentning o'zi throw qilgandek:
import { Suspense, use } from "react";
const menuPromise = new Promise((resolve, reject) => {
setTimeout(() => reject(new Error("500: server xatosi")), 300);
});
function Menu() {
const dishes = use(menuPromise);
return <p>{dishes.join(", ")}</p>;
}
export default function App() {
return (
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Menu />
</Suspense>
);
}Konsolda:
Uncaught Error: 500: server xatosiNatija oynasida — "Komponentni chizishda xato" qutisi, ilova yo'q. <Suspense> faqat kutishni ushlaydi, xatoni emas. Xatoni ushlaydigan chegara — error boundary (Error boundary, to'rt darsdan keyin). Ikkalasi odatda yonma-yon turadi: biri "kutilmoqda", ikkinchisi "xato bo'ldi".
5.2 Xatoni qiymatga aylantirish
Error boundary'siz ham yo'l bor: Promise'ning o'ziga .catch qo'shib, xatoni oddiy qiymatga aylantirish (Promise zanjiri). Shunda use hech qachon tashlamaydi. Komponent xatoni o'zi chizadi:
import { Suspense, use } from "react";
function fetchPrice(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error(`404: taom ${id} topilmadi`));
}, 200);
});
}
// xato — qiymatga aylanadi: use endi tashlamaydi
const pricePromise = fetchPrice(99).catch((err) => ({
error: err.message,
}));
function Price() {
const result = use(pricePromise);
if (result.error) {
return <p role="alert">Narx yo'q: {result.error}</p>;
}
return <p>Narx: {result} so'm</p>;
}
export default function App() {
return (
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Price />
</Suspense>
);
}Ekranda:
Narx yo'q: 404: taom 99 topilmadi.catch — Promise'ni yaratgan joyda, komponentdan tashqarida. role="alert" — Malika xatoni darhol eshitadi. Qaysi biri yaxshi? Kutilgan xato (taom topilmadi) — .catch bilan qiymat. Kutilmagan (server yiqildi) — error boundary.
5.3 try/catch ichida use — yo'q
Tabiiy fikr: "Xatoni ushlash kerakmi? try ga o'rayman". Ko'ring. Promise xatosiz hal bo'ladi — demak catch umuman ishlamasligi kerak edi. Konsolda ushlandi: qatori bor-yo'qligiga qarang:
import { Suspense, use } from "react";
const dishPromise = new Promise((resolve) => {
setTimeout(() => resolve("Osh"), 300);
});
function Menu() {
let name;
try {
name = use(dishPromise); // ❌ try ichida
} catch (err) {
console.log("ushlandi:", err.message);
name = "xato";
}
return <p>{name}</p>;
}
export default function App() {
return (
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Menu />
</Suspense>
);
}Konsolda:
ushlandi: Suspense Exception: This is not a real error! It's an implementation detail of `use` to interrupt the current render. You must either rethrow it immediately, or move the `use` call outside of the `try/catch` block. Capturing without rethrowing will lead to unexpected behavior.
To handle async errors, wrap your component in an error boundary, or call the promise's `.catch` method and pass the result to `use`.
`use` was called from inside a try/catch block. This is not allowed and can lead to unexpected behavior. To handle errors triggered by `use`, wrap your component in a error boundary.Ekranda:
xatoPromise 300 ms da "Osh" bilan muvaffaqiyatli hal bo'ldi — ekranda esa "xato". Sabab: use komponentni to'xtatish uchun ichkarida maxsus "xato" tashlaydi. catch uni tutib oldi va React to'xtashni bilmay qoldi. Xabarlar tarjimasi:
- "Suspense Exception: bu haqiqiy xato emas! Bu
usening joriy renderni to'xtatish usuli. Uni darhol qayta tashlang yokiusenitry/catchdan tashqariga chiqaring." - "Asinxron xatolar uchun komponentni error boundary'ga o'rang yoki Promise'ning
.catchmetodini chaqirib, natijasiniusega bering." - "
usetry/catchichida chaqirildi. Bu ruxsat etilmagan."
React'ning o'zi ikki yo'lni aytib turibdi — ikkalasi ham bizga tanish: error boundary va .catch.
6. use(Context)
6.1 Shart, sikl va erta return dan keyin
Context I darsida use(LangContext) ni if dan keyin chaqirgan edik. Oddiy hooklar bunday qila olmaydi (Hooklar qoidalari): React ularni chaqiruv tartibi bo'yicha taniydi. use esa hech narsani "kataklarda" saqlamaydi — u faqat o'qiydi. Shuning uchun tartib unga muhim emas:
import { createContext, use } from "react";
const CurrencyContext = createContext("so'm");
function PriceList({ items }) {
if (items.length === 0) return <p>Menyu bo'sh</p>;
const currency = use(CurrencyContext); // ✅ erta return'dan keyin
return (
<ul>
{items.map((item) => (
<li key={item.name}>
{item.name} — {item.price} {currency}
</li>
))}
</ul>
);
}
export default function App() {
return (
<CurrencyContext value="so'm">
<PriceList items={[]} />
<PriceList items={[{ name: "Osh", price: 35000 }]} />
</CurrencyContext>
);
}Ekranda:
Menyu bo'sh
Osh — 35000 so'mBirinchi PriceList Context'ni umuman o'qimadi, ikkinchisi — o'qidi. Bir xil komponent, har xil yo'l. useContext bilan bu react-hooks/rules-of-hooks lint xatosi bo'lardi. Xuddi shunday, use ni for sikli ichida ham chaqirish mumkin — masalan, bir nechta Context'ni ro'yxat bo'yicha o'qish.
Foyda qayerda? Context qimmat bo'lmasa ham, mantiq tozaroq: "ro'yxat bo'sh bo'lsa — hech narsa kerak emas" degan qoida birinchi qatorda turadi.
6.2 Faqat render paytida
use qayerda chaqirilsa bo'ladi — komponent yoki custom hook tanasida. Hodisa ishlovchisi esa render emas, u bosilganda ishlaydi:
import { createContext, use } from "react";
const CurrencyContext = createContext("so'm");
export default function App() {
function handleClick() {
const currency = use(CurrencyContext); // ❌ hodisa ichida
console.log("Osh: 35000", currency);
}
return <button onClick={handleClick}>Narxni ko'rsat</button>;
}Konsolda:
Uncaught Error: Context can only be read while React is rendering. In classes, you can read it in the render method or getDerivedStateFromProps. In function components, you can read it directly in the function body, but not inside Hooks like useReducer() or useMemo().
Error: Context can only be read while React is rendering. In classes, you can read it in the render method or getDerivedStateFromProps. In function components, you can read it directly in the function body, but not inside Hooks like useReducer() or useMemo()."Context faqat React chizayotgan paytda o'qiladi. Funksiya komponentda — to'g'ridan-to'g'ri tanasida, lekin useReducer() yoki useMemo() kabi hooklar ichida emas." Xabarning o'rtasidagi "class" haqidagi gap — eski class komponentlar uchun (Class komponentlar). Tuzatish: const currency = use(CurrencyContext); ni komponent tanasiga ko'chiring, handleClick esa tayyor currency ni ishlatsin (closure).
6.3 Qoidalar jadvali
| Qayerda | Oddiy hook (useState, useContext) |
use |
|---|---|---|
| komponent tanasi, yuqori daraja | ||
if ichida, erta return dan keyin |
||
for sikl ichida |
||
try/catch ichida |
||
| hodisa ishlovchisi, effekt |
Qisqasi: use tartibdan ozod, lekin render vaqtidan ozod emas. try/catch esa hamma hook uchun taqiq: oddiy hooklarni ham React qoidalari unga qo'ymaslikni so'raydi.
Tekshirib ko'ring:
use(Context)niuseMemo(() => use(CurrencyContext), [])ichida chaqirsa bo'ladimi?
Javob
Yo'q. useMemo ning funksiyasi render paytida ishlasa ham, xato matnining o'zi aytadi: "not inside Hooks like useReducer() or useMemo()". Context'ni tanada o'qing, natijasini useMemo ga bog'liqlik sifatida bering.
7. Serverdan uzatilgan Promise
use(promise) ning eng kuchli joyi — server bilan ishlaydigan framework'lar. Hozircha g'oyaning o'zini bilish yetarli, kodini Streaming va Suspense darsida yozamiz.
Next.js'da server komponent (serverda ishlaydigan komponent) so'rovni boshlaydi, lekin javobni kutmaydi. U Promise'ni props sifatida brauzerdagi komponentga beradi:
// Server komponent: so'rov serverda boshlanadi, kutilmaydi
export default function DishPage() {
const reviewsPromise = getReviews(1);
return (
<Suspense fallback={<p>Sharhlar yuklanmoqda…</p>}>
<Reviews reviewsPromise={reviewsPromise} />
</Suspense>
);
}
// Client komponent (alohida fayl, "use client"): brauzerda o'qiydi
function Reviews({ reviewsPromise }) {
const reviews = use(reviewsPromise);
return <p>{reviews.length} ta sharh</p>;
}Sahifa HTML'i darhol keladi — sharhlar o'rnida fallback. Sharhlar tayyor bo'lgach, server ularni o'sha javobning davomida jo'natadi. Bu — "streaming". Bugungi qoida bu yerda ham bajariladi: Promise render ichida emas, serverda bir marta yaratilgan va uzatilgan. Brauzer komponenti uni faqat o'qiydi.
8. Vazifalar qadami: batafsil statistika (2-qism)
8.1 statistika-vadasi.ts — kesh
Oldingi darsda panel va chegarani qo'shdik, statistika-vadasi.ts ni esa ochmasdan ko'chirdik. Endi uni o'qiymiz. Kod kanondan aynan, izohlar qisqa qatorlarga bo'lingan:
// statistika-vadasi.ts — batafsil statistika Promise'i (18/#04).
// use() Promise'ni kutadi, lekin uni RENDERDA YANGIDAN yaratib
// bo'lmaydi: har render yangi Promise bersa, React har safar qayta
// to'xtaydi (cheksiz kutish). Shuning uchun — kesh: bir ro'yxat
// (massiv havolasi) — bitta Promise. Ro'yxat o'zgarmasa (reducer
// o'sha massivni qaytaradi) — kesh ishlaydi; WeakMap — eski ro'yxat
// unutilsa, Promise ham xotiradan ketadi
import { batafsilHisobla } from "./batafsil-statistika.ts";
import type { BatafsilStatistika } from "./batafsil-statistika.ts";
import type { VazifaMalumoti } from "./turlar.ts";
type Vazifalar = readonly VazifaMalumoti[];
const kesh = new WeakMap<Vazifalar, Promise<BatafsilStatistika>>();
// Hisob — keyingi vazifada (setTimeout): bosish hodisasi tugab,
// React "kutilmoqda" holatini chizib ulgursin. Hisobning o'zi hali
// asosiy oqimda — 18/#24 da Web Worker'ga ko'chadi (shu funksiya
// almashadi)
function hisobla(vazifalar: Vazifalar): Promise<BatafsilStatistika> {
return new Promise((hal) => {
setTimeout(() => hal(batafsilHisobla(vazifalar)), 0);
});
}
export function statistikaVadasi(
vazifalar: Vazifalar,
): Promise<BatafsilStatistika> {
let vada = kesh.get(vazifalar);
if (vada === undefined) {
vada = hisobla(vazifalar);
kesh.set(vazifalar, vada);
}
return vada;
}Bugungi darsning hamma qoidasi shu faylda:
- Kalit — massivning o'zi (havola). Immutable yangilash tufayli ro'yxat o'zgarmasa, reducer o'sha massivni qaytaradi — demak o'sha Promise. Katak bosilsa — yangi massiv, yangi hisob.
WeakMap— eski ro'yxat hech qayerda qolmasa, uning Promise'i ham xotiradan ketadi.Mapbo'lganda har katak bosish keshda bitta "o'lik" yozuv qoldirardi.setTimeout(…, 0)— hisob darhol emas, keyingi vazifada. Bosish hodisasi tugaydi, React "Hisoblanmoqda…" ni chizib ulguradi (Event loop).hisobla— almashtiriladigan qism. Kelajakda hisob Web Worker'ga ko'chadi (Og'ir ishni UI'dan ajratish).statistikaVadasining tashqi ko'rinishi vaBatafsilStatistikao'zgarmaydi.
BatafsilStatistika.tsx da — bitta muhim qator:
const s = use(statistikaVadasi(vazifalar));Bu Sardorning use(fetchDish(id)) iga juda o'xshaydi. Farqi bitta: statistikaVadasi Promise yaratmaydi, keshdan oladi — yangisini faqat yangi ro'yxat uchun. Komponentda isLoading, useEffect, useState yo'q — faqat tayyor natija chiziladi.
8.2 Test va topilgan tuzoq
Panel uchun Vitest testi qo'shildi: tekshiruv/StatistikaPaneli.test.tsx. U soxta taymerlar bilan transition'ni qadamma-qadam tekshiradi. Bosilgandan keyin panel yopiq, fallback yo'q, holat "Hisoblanmoqda…". Taymerlar o'tkazilgach — jadval, umumiy satr 2 ta vazifa, 1 tasi bajarilgan (50%). O'rtacha uzunlik — 14 belgi.
Testni yozishda haqiqiy tuzoq chiqdi. Bosish act(…) ichida, lekin act kutilmagan (await siz) edi. Komponent transition ichida to'xtadi va React shunday dedi (xabarning birinchi gapi):
A component suspended inside an `act` scope, but the `act` call was not awaited."Komponent act doirasida to'xtadi, lekin act chaqiruvi kutilmadi." React bunday holatda to'xtagan komponentni qayta sinamaydi — test abadiy yopiq panelni ko'radi. Davosi — await act(async () => { fireEvent.click(tugma); }) va soxta taymerlarni ham await act ichida o'tkazish. Testlar chuqur — RTL II: async darsida.
8.3 Commit va tekshiruvlar
Ikki darsdagi ish — kanonda bitta commit:
git add -A
git commitCommit (kanon): feat: batafsil statistika — Suspense, use(), useTransition bilan ochish. Body: batafsil-statistika.ts (sof: teglar jami/bajarilgan, ko'p uchragan so'zlar, foiz, o'rtacha uzunlik), statistika-vadasi.ts (kesh: bir ro'yxat — bitta Promise, WeakMap), BatafsilStatistika — use(vada), StatistikaPaneli — useTransition + useDeferredValue + oldindan turgan <Suspense>.
Tekshiruvlar (haqiqiy):
npm test— node:testℹ tests 160 · ℹ pass 160(+4), VitestTests 31 passed (31)(+1, panel testi).npm run lint,format:check,tip—exit 0. Build:dist/assets/index--gAptEZQ.js 253.53 kB │ gzip: 80.38 kB(oldin 79.60 kB — statistika +0.8 kB).- Brauzer tekshiruvi — 159/159. Bir marta 158 chiqdi — bu Suspense'ga aloqasiz poyga holati edi, sababi keyinroq o'lchab tuzatildi. Yangi panel tekshiruvi (
e2e-18) — 12/12. - Diff: 9 fayl, +384 −3.
Hash (-gAptEZQ) sizda boshqa chiqadi — u fayl tarkibidan hisoblanadi.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| abadiy "Yuklanmoqda…", Promise'lar ko'payadi | Promise render ichida yaratilgan | modul, hodisa yoki kesh |
A component was suspended by an uncached promise |
o'sha xato, tez Promise bilan | kesh: bir kalit — bitta Promise |
oq ekran, Uncaught Error |
Promise rad etildi, error boundary yo'q | .catch → qiymat yoki Error boundary |
Suspense Exception: This is not a real error! |
use try/catch ichida |
try ni olib tashlang, .catch ishlating |
Context can only be read while React is rendering |
use hodisa ishlovchisida |
use ni komponent tanasiga ko'chiring |
| narx o'zgardi, ekranda eskisi | keshdagi Promise eskirgan | keshdan o'chiring yoki kutubxona ishlating |
10. Mashqlar
Mashqlar kurs/mashqlar/18/04-use/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): mumkinmi?
ha yoki yo'q deb yozing.
use(ThemeContext)niif (show) { … }ichida chaqirish mumkinmi?use(promise)nitry { … } catch { … }ichida chaqirish mumkinmi?use(ThemeContext)nionClickishlovchisi ichida chaqirish mumkinmi?use(fetchDish(id))to'g'ri, agarfetchDishPromise'ni keshdan qaytarsa?
Yechim
- ha —
usechaqiruv tartibiga bog'liq emas («Shart, sikl va ertareturndan keyin» bo'limi). - yo'q —
catchReact'ning "Suspense Exception" ini tutib oladi, to'xtash buziladi. - yo'q —
usefaqat render paytida: "Context can only be read while React is rendering". - ha — muhimi chaqiruv qayerdaligi emas, har safar o'sha Promise qaytishi. Keshli funksiya renderda chaqirilsa ham yangi Promise yaratmaydi.
2-mashq (o'rta): talonni cho'ntakka soling
Kategoriya menyusi abadiy "Yuklanmoqda…" da. Sababi — fetchMenu har renderda yangi Promise beradi. Map kesh qo'shing: har kategoriya uchun Promise bir marta yaratilsin, so'rov: logi har kategoriya uchun bir marta chiqsin. Ishora: «Map bilan kesh» bo'limidagi fetchDish.
import { Suspense, use } from "react";
const MENU = { taom: "Osh, Lag'mon, Manti", ichimlik: "Ko'k choy" };
let count = 0;
function fetchMenu(category) {
count++;
if (count <= 2) console.log("so'rov:", category);
return new Promise((resolve) => {
setTimeout(() => resolve(MENU[category]), 150);
});
}
function Menu({ category }) {
return <p>{use(fetchMenu(category))}</p>;
}
export default function App() {
return (
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Menu category="taom" />
</Suspense>
);
}Konsolda:
so'rov: taom
so'rov: taomEkranda:
Yuklanmoqda…Yechim
import { Suspense, use } from "react";
const MENU = { taom: "Osh, Lag'mon, Manti", ichimlik: "Ko'k choy" };
const cache = new Map();
function fetchMenu(category) {
if (!cache.has(category)) {
console.log("so'rov:", category);
const promise = new Promise((resolve) => {
setTimeout(() => resolve(MENU[category]), 150);
});
cache.set(category, promise);
}
return cache.get(category);
}
function Menu({ category }) {
return <p>{use(fetchMenu(category))}</p>;
}
export default function App() {
return (
<Suspense fallback={<p>Yuklanmoqda…</p>}>
<Menu category="taom" />
</Suspense>
);
}Konsolda:
so'rov: taomEkranda:
Osh, Lag'mon, MantiMenu baribir bir necha marta chizildi (urinish, prewarm, tayyor bo'lgach), lekin fetchMenu faqat birinchi chaqiruvda Promise yaratdi. Qolganlarida keshdan o'sha obyektni berdi. 150 ms da u hal bo'ldi va menyu chiqdi. Boshlang'ich koddagi count endi kerak emas — konsolni cheklash uchun edi.
3-mashq (qiyin): tugma bilan yuklash va xato
Ikki tugma: «Osh» va «Taom 99». Bosilganda narx serverdan olinadi (fetchPrice tayyor — o'zgartirmang). Talablar:
- Promise hodisa ishlovchisida yaratilsin va state'da saqlansin.
Pricekomponenti uni props'dan olib,usebilan o'qisin. - Kutish paytida
<Suspense>fallback'i: "Yuklanmoqda…". - Taom topilmasa ilova yiqilmasin:
role="alert"li<p>da xato matni chiqsin. Ishora: «Xatoni qiymatga aylantirish» bo'limi —.catchPromise'ni yaratgan joyda.
const PRICES = { 1: 35000, 4: 30000 };
function fetchPrice(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (PRICES[id]) resolve(PRICES[id]);
else reject(new Error(`404: taom ${id} topilmadi`));
}, 200);
});
}Yechim
import { Suspense, use, useState } from "react";
const PRICES = { 1: 35000, 4: 30000 };
function fetchPrice(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (PRICES[id]) resolve(PRICES[id]);
else reject(new Error(`404: taom ${id} topilmadi`));
}, 200);
});
}
function Price({ pricePromise }) {
const result = use(pricePromise);
if (result.error) return <p role="alert">{result.error}</p>;
return <p>Narx: {result} so'm</p>;
}
export default function App() {
const [pricePromise, setPricePromise] = useState(null);
function load(id) {
const promise = fetchPrice(id).catch((err) => ({
error: err.message,
}));
setPricePromise(promise);
}
return (
<div>
<button onClick={() => load(1)}>Osh</button>{" "}
<button onClick={() => load(99)}>Taom 99</button>
<Suspense fallback={<p>Yuklanmoqda…</p>}>
{pricePromise && <Price pricePromise={pricePromise} />}
</Suspense>
</div>
);
}Ekranda («Taom 99» dan 400 ms keyin):
Osh Taom 99
404: taom 99 topilmadi«Osh» bosilsa — Narx: 35000 so'm. Promise har bosishda bir marta yaratiladi va state'da turadi — har render o'sha obyektni oladi. .catch rad etilishni { error } obyektiga aylantiradi, shuning uchun use tashlamaydi. Tugmalar ketma-ket bosilsa-chi? Har bosish yangi Promise'ni state'ga yozadi. React eng oxirgisini ko'rsatadi — useEffect III dagi poyga muammosi bu yerda o'z-o'zidan yo'q. Tugmani transition bilan o'rasangiz (Suspense), eski narx ham kutish paytida joyida qoladi.
4-mashq: Vazifalar qadami
feat/batafsil-statistika branch'ida statistika-vadasi.ts ni «Vazifalar qadami» bo'limi bo'yicha o'qib chiqing va panel testini qo'shing. Keyin commit qiling va PR oching.
Yechim
Tekshiruv ro'yxati:
npm test— node 160, Vitest 31.npm run lint,format:check,tip— toza.- Tajriba (keyin qaytaring):
statistikaVadasiichida keshni chetlab, har safarhisobla(vazifalar)qaytaring vanpm run devda panelni oching. KonsoldaA component was suspended by an uncached promise…chiqadi. Keshni qaytaring — ogohlantirish yo'qoladi. npm run build— boshlang'ich JS gzip ~80 kB,npm run previewda panel ochiladi, fallback ko'rinmaydi.- Commit —
feat: batafsil statistika — Suspense, use(), useTransition bilan ochish. PR'ni birlashtiring.
11. Real ishda
- TanStack Query, SWR, Relay — ichida "bir kalit — bitta Promise" keshi bor.
useSuspenseQueryaslida keshdan Promise olib, React'ga beradi (TanStack Query I). BugungiMap— ularning kichik modeli. - Next.js — server komponent Promise'ni boshlaydi, client komponent
usebilan o'qiydi (Streaming va Suspense). Bu —use(promise)ning asosiy "uyi". use(Context)— yangi koddauseContexto'rnida tobora ko'p uchraydi, kutubxonalar ham o'tyapti. Ikkalasi ham ishlaydi.- Kod ko'rigi (code review) da birinchi savol: "Bu Promise qayerda yaratilgan?". Render ichida bo'lsa — darhol belgi.
- Intervyu savollari: "
usevauseContextfarqi?", "negauseni shart ichida chaqirsa bo'ladi?", "nega Promise'ni render ichida yaratib bo'lmaydi?", "userad etilgan Promise bilan nima qiladi?".
Xulosa
use(promise): pending — to'xtatadi, fulfilled — qiymat, rejected — xato tashlaydi.- React Promise'ga
status/valuebelgisini qo'yadi. O'sha obyekt ikkinchi marta — darhol, yangi obyekt — har safar kutish. - Promise render ichida yaratilmaydi: modul, hodisa (+ state) yoki kesh — "bir kalit, bitta Promise".
- Rad etilish:
.catchbilan qiymatga aylantiring yoki error boundary qo'ying.try/catchichidause— yo'q. use(Context)—if, sikl va ertareturndan keyin ham, lekin faqat render paytida.vazifalar:statistika-vadasi.ts—WeakMapkeshi, kalit — ro'yxat massivi. Node 160, Vitest 31, brauzer 159/159.
Keyingi dars: Actions: async transitionlar va form action — transition ichidagi async funksiya, <form action> va React o'zi boshqaradigan "kutilmoqda" holati.
Manbalar
- React:
use— react.dev/reference/react/use (2026-10 holatiga, React 19.3) - React:
<Suspense>— "Showing stale content while fresh content is loading" — react.dev/reference/react/Suspense - React:
useContext— react.dev/reference/react/useContext - MDN:
WeakMap— developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!