Mundarija (40)
- Bu darsda
- 1. Nega bu kerak?
- 2. loader — ma'lumot sahifadan oldin
- 2.1 Birinchi loader
- 2.2 Ikki yo'l yonma-yon
- 2.3 Ichma-ich loader'lar parallel
- 3. Kutish paytida nima ko'rinadi?
- 3.1 useNavigation — butun ilova uchun bitta holat
- 3.2 Birinchi ochilish: hydrateFallbackElement
- 4. action va <Form> — o'zgartirish
- 4.1 Forma router'ga yuboriladi
- 4.2 Bitta action — bir nechta amal: niyat
- 4.3 Xato qaytarish: useActionData va status
- 4.4 Amaldan keyin boshqa sahifa: redirect
- 5. useFetcher — sahifadan ketmasdan
- 5.1 Navigatsiya bo'lmasligi kerak bo'lganda
- 5.2 Fetcher imkoniyatlari
- 6. Xatolar: errorElement
- 6.1 Loader xato tashlasa
- 6.2 Xato yuqoriga ko'tariladi
- 7. Bekor qilish va kod bo'lish
- 7.1 request.signal
- 7.2 lazy — marshrut kodi kerak bo'lganda
- 8. Vazifalar qadami: vazifa sahifasi serverdan (holat 05)
- 8.1 Nima quramiz
- 8.2 Loader: vazifa-marshruti.ts
- 8.3 Action: belgilash va o'chirish
- 8.4 Sahifa: VazifaUmumiy.tsx da formalar
- 8.5 Marshrut va xato sahifasi
- 8.6 ESLint: throw faqat Error emas
- 8.7 Tekshiruvlar
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): kim, qachon?
- 2-mashq (o'rta): effektdan loader'ga
- 3-mashq (qiyin): stol band qilish
- 4-mashq: Vazifalar qadami
- 11. Real ishda
- Xulosa
- Manbalar
React Router data mode: loader, action, Form, useFetcher va errorElement
Qisqacha: Data mode'da har marshrutga
loader(ma'lumotni olish) vaaction(o'zgartirish) funksiyasi biriktiriladi. Router sahifani chizishdan oldin loader'ni chaqiradi, komponent natijaniuseLoaderData()bilan oladi —useEffectva "yuklanmoqda" state'i kerak emas.<Form method="post">yuborilganda routeractionni chaqiradi, keyin sahifadagi loader'larni qayta ishga tushiradi. Xato tashlansa (throw data("…", { status: 404 })), sahifa o'rnida eng yaqinerrorElementchiziladi.
Bu darsda
loaderyozasiz vauseLoaderDatabilan sahifaga ma'lumot berasiz; nega buuseEffectdagi yuklashdan tezroq ekanini tushuntirasiz.useNavigationbilan butun ilova uchun "yuklanmoqda" belgisini,hydrateFallbackElementbilan birinchi ochilishdagi holatni ko'rsatasiz.<Form>vaactionbilan formani yuborasiz,useActionDatabilan xatoni qaytarasiz,redirectbilan boshqa sahifaga o'tasiz.useFetcherbilan sahifadan ketmasdan amal bajarasiz ("Yoqdi" tugmasi).errorElement,useRouteErrorvaisRouteErrorResponsebilan 404 va kutilmagan xatoni chiroyli ko'rsatasiz;vazifalardagi vazifa sahifasini serverga ulaysiz.
Oldin bilishingiz kerak: React Router III: dasturiy navigatsiya va URL holati, Effektda ma'lumot olish, Actions va form action, Error boundary.
1. Nega bu kerak?
«Bahor» saytida taom sahifasi bor: /taom/osh. Hozircha taomlar kodning ichida yozilgan. Jasur aka endi narxlarni serverdan olmoqchi — oshxonada narx o'zgarsa, sayt darhol bilsin.
Sardor buni effekt darsidagidek yozdi: komponent chiziladi, useEffect so'rov yuboradi, javob kelguncha "Yuklanmoqda…". Ishlaydi. Lekin uchta muammo chiqdi:
- Kech boshlanadi. So'rov komponent chizilgandan keyin ketadi. Undan oldin router sahifani tanladi, React chizdi — vaqt o'tdi.
- Zinapoya (waterfall). Taom sahifasi ichida sharhlar bo'limi bor. U ham o'z
useEffectida so'raydi — lekin faqat ota chizilgandan keyin. So'rovlar ketma-ket, parallel emas. - Har sahifada bir xil kod.
loading,error,datauch state'i, bekor qilish, poyga holati (race) — har sahifada qaytadan.
Router esa manzilni bosilgan zahoti biladi: /taom/osh — demak taom sahifasi, unga osh kerak. Nega so'rovni darhol yubormaslik kerak? Data mode aynan shuni qiladi.
Hayotiy o'xshatish — oshxonadagi ofitsiant. Yomon ofitsiant mehmonni stolga o'tqazadi, keyin oshxonaga borib buyurtma beradi, keyin choynakka ketadi. Yaxshisi eshikdanoq buyurtmani oshpazga aytadi va choyni ham shu payt qo'yadi. Mehmon o'tirganda hammasi tayyor bo'ladi. loader — eshikdagi buyurtma.
2. loader — ma'lumot sahifadan oldin
2.1 Birinchi loader
Loader — marshrutga biriktirilgan funksiya. Router shu marshrutga o'tishdan oldin uni chaqiradi va qaytgan qiymatni (Promise bo'lsa — natijasini) kutadi. Komponent esa qiymatni useLoaderData() bilan oladi.
Natija oynasida tarmoq yo'q, shuning uchun "server" — 250 ms kechikadigan Promise. Loyihada uning o'rnida fetch turadi.
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useLoaderData,
useNavigation,
} from "react-router";
// Soxta server: 250 ms dan keyin javob
const wait = (ms, value) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
function Shell() {
const navigation = useNavigation();
const busy = navigation.state === "loading";
return (
<div>
<p>{busy ? "Yuklanmoqda…" : "Tayyor"}</p>
<Outlet />
</div>
);
}
function Dish() {
const dish = useLoaderData();
console.log("chizildi:", dish.name);
return (
<p>
{dish.name} — {dish.price} so'm
</p>
);
}
const router = createMemoryRouter([
{
path: "/",
element: <Shell />,
children: [
{ index: true, element: <Link to="/taom/osh">Osh</Link> },
{
path: "taom/:slug",
loader: ({ params }) => {
console.log("loader:", params.slug);
return wait(250, { name: "Osh", price: 35000 });
},
element: <Dish />,
},
],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
loader: osh
chizildi: OshEkranda:
Tayyor
Osh — 35000 so'm«Osh» bosildi — va sahifa darhol almashmadi. Router 250 ms davomida eski sahifada qoldi, yuqorida "Yuklanmoqda…" turdi (harakat qatoridagi ekran("Yuklanmoqda…") shuni tekshiradi). Javob kelgach, Dish birinchi chizilishidayoq tayyor ma'lumot bilan chizildi. Konsolga qarang: chizildi faqat bir marta, va u loader'dan keyin.
Komponentda useState yo'q, useEffect yo'q, if (loading) yo'q. dish hech qachon undefined emas — router uni kafolatlaydi.
Loader'ga router bitta obyekt beradi:
| Maydon | Nima | Misol |
|---|---|---|
params |
dinamik segmentlar | params.slug — "osh" |
request |
Fetch API'ning Request obyekti |
request.url, request.signal |
request.url dan so'rov parametrlari olinadi: new URL(request.url).searchParams.get("turi"). request.signal haqida — «Bekor qilish» bo'limida.
2.2 Ikki yo'l yonma-yon
Effekt darsida tartib shunday edi: chizish ("Yuklanmoqda…") → so'rov → yana chizish. Loader bilan: so'rov → chizish, bitta chizish. Ikki sahifa ichma-ich bo'lsa, farq ikki barobar bo'ladi:
sequenceDiagram
participant R as Router
participant S as Server
Note over R,S: useEffect bilan
R->>R: taom sahifasi chizildi
R->>S: taom?
S-->>R: osh
R->>R: sharhlar chizildi
R->>S: sharhlar?
S-->>R: 3 ta sharh
Note over R,S: loader bilan
R->>S: taom? va sharhlar? (bir vaqtda)
S-->>R: osh, 3 ta sharh
R->>R: hammasi chizildiYuqorida — zinapoya: har qavat o'z navbatini kutadi. Pastda — ikkala so'rov birga ketdi.
2.3 Ichma-ich loader'lar parallel
Router mos kelgan barcha marshrutlarni oldindan biladi (/ → taom/:slug → sharhlar). Shuning uchun ularning loader'larini bir vaqtda chaqiradi:
import {
createMemoryRouter,
Link,
Outlet,
RouterProvider,
useLoaderData,
} from "react-router";
const wait = (ms, value) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
function log(text) {
console.log(text);
}
function Dish() {
const dish = useLoaderData();
return (
<section>
<h2>{dish}</h2>
<Outlet />
</section>
);
}
function Reviews() {
const reviews = useLoaderData();
return <p>Sharhlar: {reviews.join(", ")}</p>;
}
const router = createMemoryRouter([
{
path: "/",
element: <Link to="/taom/osh/sharhlar">Osh sharhlari</Link>,
},
{
path: "/taom/:slug",
element: <Dish />,
loader: async () => {
log("taom: boshlandi");
const dish = await wait(200, "Osh");
log("taom: tugadi");
return dish;
},
children: [
{
path: "sharhlar",
element: <Reviews />,
loader: async () => {
log("sharhlar: boshlandi");
const list = await wait(300, ["mazali", "issiq"]);
log("sharhlar: tugadi");
return list;
},
},
],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
taom: boshlandi
sharhlar: boshlandi
taom: tugadi
sharhlar: tugadiEkranda:
Osh
Sharhlar: mazali, issiqIkkala "boshlandi" birinchi chiqdi — so'rovlar parallel. Umumiy kutish 200 + 300 emas, eng uzuni — 300 ms. Har komponent useLoaderData() bilan o'z marshrutining ma'lumotini oladi: Dish — taomni, Reviews — sharhlarni.
Maslahat: Bola komponentga ota marshrutning ma'lumoti kerak bo'lsa —
useRouteLoaderData("taom"). Buning uchun ota marshrutgaid: "taom"beriladi. Ikkinchi yo'l — oldingi darsdagi<Outlet context>.
Tekshirib ko'ring: Taom loader'i 200 ms, sharhlar loader'i 300 ms.
useEffectbilan (sharhlar ota chizilgandan keyin so'raladi) va loader bilan sahifa qancha vaqtda to'liq chiziladi?
Javob
useEffect bilan — taxminan 500 ms: avval taom (200), u chizilgach sharhlar so'raladi (yana 300). Loader bilan — 300 ms: ikkalasi bir vaqtda boshlanadi, router eng sekinini kutadi. Sahifa qancha ko'p qavatli bo'lsa, farq shuncha katta.
3. Kutish paytida nima ko'rinadi?
3.1 useNavigation — butun ilova uchun bitta holat
Loader ishlayotganda router eski sahifani ekranda ushlab turadi. Foydalanuvchiga "bosdim, nimadir bo'lyapti" degan belgi kerak. useNavigation() hozirgi o'tishning holatini beradi:
navigation.state |
Qachon |
|---|---|
"idle" |
hech narsa kutilmayapti |
"loading" |
yangi sahifaning loader'lari ishlayapti |
"submitting" |
forma yuborildi, action ishlayapti |
Birinchi misoldagi Shell shuni ishlatdi. Bu hook istalgan komponentda ishlaydi — odatda layout'da: sahifa tepasidagi ingichka chiziq, xira qilingan kontent. navigation.location — qayerga ketilyapti.
Eski sahifa ekranda turishi — ataylab. Transition darsidagi g'oyaning o'zi: tayyor bo'lmagan sahifa o'rniga bo'sh ekran ko'rsatgandan ko'ra, eskisini ushlab turish yaxshiroq.
3.2 Birinchi ochilish: hydrateFallbackElement
Do'st havolani Telegram'da oldi va /taom/osh ni to'g'ridan ochdi. Endi "eski sahifa" yo'q — ilova shu zahotida boshlandi. Loader 250 ms ishlaydi. Shu paytda ekranda nima bo'ladi?
import {
createMemoryRouter,
RouterProvider,
useLoaderData,
} from "react-router";
const wait = (ms, value) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
function Dish() {
const dish = useLoaderData();
return <p>{dish.name}</p>;
}
const router = createMemoryRouter(
[
{
path: "/taom/:slug",
loader: () => wait(250, { name: "Osh" }),
element: <Dish />,
},
],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
No `HydrateFallback` element provided to render during initial hydrationEkranda:
OshSahifa chizildi, lekin 250 ms davomida ekran bo'sh edi va konsolda ogohlantirish chiqdi. Tarjimasi: "Dastlabki yuklanish paytida chiziladigan HydrateFallback elementi berilmagan". Davosi — marshrutga hydrateFallbackElement:
import {
createMemoryRouter,
RouterProvider,
useLoaderData,
} from "react-router";
const wait = (ms, value) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
function Dish() {
const dish = useLoaderData();
return <p>{dish.name}</p>;
}
const router = createMemoryRouter(
[
{
path: "/taom/:slug",
loader: () => wait(250, { name: "Osh" }),
hydrateFallbackElement: <p role="status">Yuklanmoqda…</p>,
element: <Dish />,
},
],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
OshAvval "Yuklanmoqda…" (ekran qadami tekshirdi), keyin osh. Ogohlantirish yo'q. role="status" — Malika (ekran o'quvchi) ham holatni eshitadi.
Diqqat: Birinchi ochilishda loader har doim sahifani kechiktiradi. Ro'yxat kabi asosiy sahifada bu sezilarli: foydalanuvchi bo'sh ekranga qaraydi, keyin kontent "sakraydi" (CLS — Core Web Vitals). Shuning uchun
vazifalarda ro'yxat sahifasiga loader qo'yilmadi — u brauzerdagi nusxadan darhol chiziladi. Loader faqat vazifa sahifasida.
4. action va <Form> — o'zgartirish
4.1 Forma router'ga yuboriladi
Ma'lumot olish — yarmi. Mehmon sharh ham yozadi. HTML formasini eslang (Forma qanday ishlaydi): <form method="post"> yuborilsa, brauzer ma'lumotni serverga jo'natib, yangi sahifani yuklardi. React Router'ning <Form> komponenti xuddi shunday yoziladi, lekin yuborishni brauzer emas, router ushlaydi. Router ma'lumotni marshrutning action funksiyasiga beradi. Sahifa qayta yuklanmaydi.
import {
createMemoryRouter,
Form,
RouterProvider,
useLoaderData,
useNavigation,
} from "react-router";
const wait = (ms, value) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
// "Server" xotirasi
const reviews = ["Issiq keldi"];
function Reviews() {
const list = useLoaderData();
const busy = useNavigation().state === "submitting";
return (
<section>
<ul>
{list.map((text) => (
<li key={text}>{text}</li>
))}
</ul>
<Form method="post">
<input name="text" aria-label="Sharh" />
<button disabled={busy}>
{busy ? "Yuborilmoqda…" : "Yuborish"}
</button>
</Form>
</section>
);
}
const router = createMemoryRouter([
{
path: "/",
element: <Reviews />,
loader: () => {
console.log("loader");
return [...reviews];
},
action: async ({ request }) => {
const form = await request.formData();
console.log("action:", form.get("text"));
await wait(250);
reviews.push(form.get("text"));
return null;
},
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
loader
action: Juda mazali
loaderEkranda:
Issiq keldi
Juda mazali
YuborishKonsoldagi uch qatorni ketma-ket o'qing:
loader— sahifa ochildi.action: Juda mazali— forma yuborildi.request.formData()— inputlarningnameva qiymatlari, oddiy HTML formadagidek (FormData).loader— yana. Action tugagach router sahifadagi loader'larni o'zi qayta chaqirdi. Bu — revalidatsiya (revalidation): "ma'lumot o'zgargan bo'lishi mumkin, qaytadan olaman".
setState yo'q: server o'zgardi, router yangisini oldi. Shu paytda useNavigation().state avval "submitting", keyin "loading" (revalidatsiya), oxirida "idle" bo'ldi.
Bu Actions darsidagi <form action={fn}> ga o'xshaydi. Farqi: React'ning form action'i — komponent ichidagi funksiya. Router action'i — marshrutga bog'langan va tugagach ma'lumotni o'zi yangilaydi.
4.2 Bitta action — bir nechta amal: niyat
Bir sahifada ikki forma bo'lishi mumkin: "Belgilash" va "O'chirish". Action esa bitta. Ularni ajratish uchun yashirin maydon qo'yiladi:
<Form method="post">
<input type="hidden" name="niyat" value="ochir" />
<button>O'chirish</button>
</Form>Action ichida: const niyat = form.get("niyat") va if (niyat === "ochir") …. Ingliz tilidagi hujjatlarda bu naqsh intent deyiladi. vazifalar da aynan shunday qilinadi.
4.3 Xato qaytarish: useActionData va status
Bo'sh sharh yuborilsa-chi? Action xatoni qaytaradi (tashlamaydi), komponent uni useActionData() bilan o'qiydi:
import {
createMemoryRouter,
data,
Form,
RouterProvider,
useActionData,
useLoaderData,
} from "react-router";
function Reviews() {
const list = useLoaderData();
const result = useActionData();
return (
<Form method="post">
<p>Sharhlar: {list.length} ta</p>
<input name="text" aria-label="Sharh" />
<button>Yuborish</button>
{result?.error && <p role="alert">{result.error}</p>}
</Form>
);
}
const router = createMemoryRouter([
{
path: "/",
element: <Reviews />,
loader: () => {
console.log("loader");
return ["Issiq keldi"];
},
action: async ({ request }) => {
const text = String((await request.formData()).get("text"));
console.log("action:", JSON.stringify(text));
if (text.trim() === "") {
const error = "Sharh bo'sh bo'lmasin";
return data({ error }, { status: 400 });
}
return null;
},
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
loader
action: ""Ekranda:
Sharhlar: 1 ta
Yuborish
Sharh bo'sh bo'lmasinIkki narsaga qarang. Birinchisi — useActionData() action qaytargan obyektni berdi, xato ekranga chiqdi. Ikkinchisi — konsolda loader ikkinchi marta chaqirilmadi. data(…, { status: 400 }) — "javob, lekin xato holati bilan". 4xx yoki 5xx status bilan qaytgan action'dan keyin router revalidatsiya qilmaydi: hech narsa o'zgarmadi, qayta so'rash — ortiqcha. Status'siz oddiy return { error } qilsangiz, loader baribir qayta ishlaydi.
Maslahat: Bu tekshiruv — foydalanuvchiga qulaylik. Haqiqiy serverda ham xuddi shu tekshiruv bo'lishi shart: brauzerdagi kodni har kim chetlab o'tishi mumkin. Forma tekshiruvining to'liq vositalari — React Hook Form va Zod darslarida.
4.4 Amaldan keyin boshqa sahifa: redirect
Buyurtma berilgach — tasdiq sahifasiga. Action ichida hook yo'q (useNavigate ishlamaydi — u komponent emas). O'rniga redirect("/manzil") qaytariladi:
import {
createMemoryRouter,
Form,
redirect,
RouterProvider,
useParams,
} from "react-router";
let lastId = 11;
function Order() {
const { id } = useParams();
return <p>{id}-buyurtma qabul qilindi!</p>;
}
const router = createMemoryRouter([
{
path: "/",
element: (
<Form method="post">
<button>Buyurtma berish</button>
</Form>
),
action: () => {
lastId += 1; // serverga saqlash o'rnida
return redirect(`/buyurtma/${lastId}`);
},
},
{ path: "/buyurtma/:id", element: <Order /> },
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
12-buyurtma qabul qilindi!Oldingi darsdagi navigate("/buyurtma/12") misolining data mode varianti. redirect tarixga yangi yozuv qo'shadi (push). Joriy yozuvni almashtirish kerak bo'lsa — replace("/manzil"): bu navigate(…, { replace: true }) ning loader/action'dagi ko'rinishi. Loader ham redirect yoki replace qaytarishi (yoki tashlashi) mumkin — masalan, eski manzildan yangisiga.
Tekshirib ko'ring: Action
return { ok: true }qaytardi. Sahifadagi loader qayta chaqiriladimi?return data({ ok: false }, { status: 500 })bo'lsa-chi?
Javob
Birinchisida — ha: muvaffaqiyatli action'dan keyin router hamma faol loader'larni qayta chaqiradi (revalidatsiya). Ikkinchisida — yo'q: 4xx/5xx status "hech narsa o'zgarmadi" degan ma'noni beradi. Ikkala holatda ham useActionData() qaytgan obyektni beradi.
5. useFetcher — sahifadan ketmasdan
5.1 Navigatsiya bo'lmasligi kerak bo'lganda
Menyuda har taom yonida «Yoqdi » tugmasi. Bosilsa — serverga yoziladi. Lekin <Form> navigatsiya: u action'ga yuborib, keyin o'sha marshrutga o'tadi, tarixga yozuv qo'shishi va useNavigation ni butun ilovada "submitting" qilishi mumkin. Yoqtirish uchun bu ortiqcha — mehmon menyuda qolishi kerak.
useFetcher() — navigatsiyasiz loader/action chaqiruvchi. Har chaqiruv o'zining kichik "kanali" ga ega: o'z state i, o'z data si. Bir sahifada nechta bo'lsa ham, bir-biriga xalaqit bermaydi.
import {
createMemoryRouter,
RouterProvider,
useFetcher,
useLoaderData,
} from "react-router";
const wait = (ms, value) =>
new Promise((resolve) => setTimeout(() => resolve(value), ms));
const likes = { osh: 4, manti: 2 };
function DishRow({ slug, name, count }) {
const fetcher = useFetcher();
return (
<li>
{name}: {count} ta{" "}
<fetcher.Form method="post" action={`/taom/${slug}/yoqtir`}>
<button>
{fetcher.state === "idle" ? "Yoqdi ❤️" : "…"}
</button>
</fetcher.Form>
</li>
);
}
function Menu() {
const data = useLoaderData();
return (
<ul>
<DishRow slug="osh" name="Osh" count={data.osh} />
<DishRow slug="manti" name="Manti" count={data.manti} />
</ul>
);
}
const router = createMemoryRouter([
{
path: "/",
element: <Menu />,
loader: () => {
console.log("menyu loader");
return { ...likes };
},
},
{
// Faqat action — sahifa (element) yo'q
path: "/taom/:slug/yoqtir",
action: async ({ params }) => {
await wait(250);
likes[params.slug] += 1;
console.log("yoqtirildi:", params.slug);
return null;
},
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
menyu loader
yoqtirildi: osh
menyu loaderEkranda:
Osh: 5 ta
Yoqdi ❤️
Manti: 2 ta
Yoqdi ❤️Faqat bosilgan tugma "…" ga aylandi — boshqa qator tegilmadi (fetcher.state shu qatorniki). Manzil / da qoldi. Action tugagach menyu loader'i baribir qayta ishladi va son 5 bo'ldi: fetcher ham revalidatsiyani boshlaydi.
/taom/:slug/yoqtir marshrutida element yo'q — faqat action. Bunday marshrut resurs marshrut deyiladi: unga sahifa sifatida o'tilmaydi, faqat ma'lumot uchun murojaat qilinadi.
5.2 Fetcher imkoniyatlari
| Nima | Vazifasi |
|---|---|
<fetcher.Form> |
<Form> ning navigatsiyasiz turi |
fetcher.submit(formData, { method, action }) |
koddan yuborish (masalan, onChange da) |
fetcher.load("/manzil") |
boshqa marshrutning loader'ini chaqirish (qidiruv takliflari) |
fetcher.state |
"idle", "submitting", "loading" |
fetcher.data |
action/loader qaytargan qiymat |
fetcher.formData |
yuborilayotgan ma'lumot — optimistik UI uchun |
Oxirgisi qiziq. Bosilgan zahoti sonni oshirib ko'rsatish mumkin — server javobini kutmasdan: const shown = fetcher.formData ? count + 1 : count. Bu useOptimistic g'oyasining router'dagi ko'rinishi. Server rad etsa, formData yo'qoladi va son o'z-o'zidan eski holiga qaytadi.
Maslahat: Qachon
<Form>, qachonfetcher? Savol bering: "Amaldan keyin foydalanuvchi boshqa sahifada bo'lishi kerakmi yoki manzil o'zgarishi kerakmi?" Ha —<Form>(yangi buyurtma, kirish, tahrirni saqlash). Yo'q —fetcher(yoqtirish, katakni belgilash, ro'yxatdagi qatorni o'chirish).
6. Xatolar: errorElement
6.1 Loader xato tashlasa
/taom/pitsa — bunday taom yo'q. Loader nima qiladi? Bo'sh obyekt qaytarsa, komponent undefined.name da yiqiladi. To'g'ri yo'l — tashlash (throw). Router uni ushlaydi va sahifa o'rniga marshrutning errorElement ini chizadi. Komponent ichida xatoni useRouteError() beradi.
Xato ikki xil bo'ladi va ularni isRouteErrorResponse() ajratadi:
- javob-xato — siz ataylab tashlagan
throw data("Taom topilmadi", { status: 404 }):status,statusText,datamaydonlari bor; - kutilmagan xato —
TypeError, tarmoq uzilishi va hokazo oddiyErrorobyektlari.
import {
createMemoryRouter,
data,
isRouteErrorResponse,
Link,
Outlet,
RouterProvider,
useLoaderData,
useRouteError,
} from "react-router";
const DISHES = { osh: "Osh", manti: "Manti" };
function Shell() {
return (
<div>
<p>«Bahor» menyusi</p>
<Outlet />
</div>
);
}
function Dish() {
return <p>{useLoaderData()}</p>;
}
function DishError() {
const error = useRouteError();
if (isRouteErrorResponse(error)) {
return (
<p>
{error.status}: {error.data}
</p>
);
}
return <p>Kutilmagan xato: {error.message}</p>;
}
const router = createMemoryRouter([
{
path: "/",
element: <Shell />,
children: [
{ index: true, element: <Link to="/taom/pitsa">Pitsa</Link> },
{
path: "taom/:slug",
element: <Dish />,
errorElement: <DishError />,
loader: ({ params }) => {
const dish = DISHES[params.slug];
if (dish === undefined) {
throw data("Bunday taom yo'q", { status: 404 });
}
return dish;
},
},
],
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
«Bahor» menyusi
404: Bunday taom yo'qShell joyida qoldi — "«Bahor» menyusi" ko'rinib turibdi. Xato faqat taom/:slug marshrutining o'rnini egalladi. Konsolda hech narsa yo'q: o'zimiz ushlagan xato "kutilgan" holat.
errorElement — Error boundary darsidagi chegara, faqat marshrutga bog'langan. U loader va action xatolarini ham, sahifa komponenti chizilishidagi xatoni ham ushlaydi.
6.2 Xato yuqoriga ko'tariladi
Marshrutda errorElement bo'lmasa, xato otasiga ko'tariladi — eng yaqin errorElement gacha. Yuqoridagi misolda errorElement ni Shell ga ko'chirsak, xato butun Shell ni almashtiradi: menyu sarlavhasi ham yo'qoladi. Qoida: errorElement ni iloji boricha pastga qo'ying, shunda ilovaning ishlab turgan qismi ko'rinib qoladi.
Hech qayerda errorElement bo'lmasa-chi? Router o'zining standart sahifasini chizadi:
import {
createMemoryRouter,
data,
RouterProvider,
useLoaderData,
} from "react-router";
function Dish() {
return <p>{useLoaderData()}</p>;
}
const router = createMemoryRouter(
[
{
path: "/taom/:slug",
element: <Dish />,
loader: () => {
throw data("Server javob bermadi", { status: 500 });
},
},
],
{ initialEntries: ["/taom/osh"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
Error handled by React Router default ErrorBoundary: ErrorResponseImpl {status: 500, statusText: 'Internal Server Error', data: 'Server javob bermadi', error: undefined, internal: false}Ekranda:
Unexpected Application Error!
500 Internal Server Error
💿 Hey developer 👋
You can provide a way better UX than this when your app throws errors by providing your own ErrorBoundary or errorElement prop on your route.Ekranda — "Kutilmagan ilova xatosi!" va status. Pastdagi " Hey developer " qatorlari — dasturchiga maslahat: "xato bo'lganda o'z ErrorBoundary yoki errorElement ingizni bersangiz, foydalanuvchiga ancha yaxshi tajriba berasiz". Konsoldagi qator ham shu haqda: "xatoni React Router'ning standart ErrorBoundary'si ushladi", keyin xato obyekti (ErrorResponseImpl — data() ning ichki nomi). Maslahat va konsol qatori faqat development build'da. Production'da foydalanuvchi faqat "Unexpected Application Error!" va statusni ko'radi — inglizcha va yordamsiz. Shuning uchun ildiz marshrutda doim o'z errorElement ingiz bo'lsin (vazifalar da u 2-darsdan beri bor — XatoSahifasi).
Tekshirib ko'ring: Loader'da
throw new Error("…")o'rniga negathrow data("…", { status: 404 })yozdik? Ikkalasi hamerrorElementga tushadi-ku.
Javob
data(…, { status }) — "kutilgan" xato: taom yo'q, ruxsat yo'q. errorElement uni isRouteErrorResponse bilan taniydi va holatga mos matn chiqaradi: 404 — "topilmadi", 403 — "ruxsat yo'q". Oddiy Error — "nimadir buzildi", unga boshqacha munosabat kerak (qayta urinish, xabar berish). Ikkalasini bir xil tashlasak, foydalanuvchiga "Bunday taom yo'q" ni "Server buzildi" dan ajratib ko'rsata olmaymiz.
7. Bekor qilish va kod bo'lish
7.1 request.signal
Mehmon osh sahifasiga bosdi, loader so'rov yubordi. Yarim soniyadan keyin fikrini o'zgartirib, manti'ni bosdi. Osh javobi endi kerak emas. Router eskirgan o'tishni o'zi tashlab yuboradi va request.signal ni bekor qiladi. Uni fetch(url, { signal: request.signal }) ga bersangiz, brauzer so'rovning o'zini ham to'xtatadi (AbortController). signal bermasangiz ham ekranda xato bo'lmaydi — router eski javobni e'tiborsiz qoldiradi. Lekin trafik behuda ketadi.
7.2 lazy — marshrut kodi kerak bo'lganda
Marshrutga lazy berilsa, uning komponenti va loader'i alohida faylga ajratiladi va birinchi o'tishda yuklanadi:
{
path: "statistika",
lazy: async () => {
const m = await import("./sahifalar/StatistikaSahifasi.tsx");
return { Component: m.StatistikaSahifasi };
},
}Natija oynasida dinamik import() ishlamaydi, shuning uchun blok tekshirilmagan. lazy ning g'oyasi — lazy va kodni bo'lish darsidagi React.lazy bilan bir xil, lekin router uni loader bilan parallel yuklaydi.
Kanonda vazifalar ning statistika va sozlamalar sahifalariga lazy sinab ko'rildi (19/#17, natijalar/17-lazy-marshrut.txt). Sahifalar kodi kichik (0.6 va 0.3 KB gzip), Vite esa umumiy modullarni alohida faylga ajratdi: ro'yxat sahifasi 130.5 KB yukladi (lazy'siz ~129.8) va bitta ortiqcha so'rov. Foyda yo'q — kanonga kirmadi. lazy og'ir sahifalar uchun (grafik kutubxonasi, matn muharriri), kichik sahifani ajratish o'zini oqlamaydi.
8. Vazifalar qadami: vazifa sahifasi serverdan (holat 05)
8.1 Nima quramiz
Hozir vazifa sahifasi ro'yxatni brauzerdagi nusxadan (yukla()) o'qiydi. Boshqa qurilmada o'zgargan vazifa bu yerda eski ko'rinadi. Endi:
- Loader vazifani serverdan oladi; oflayn yoki xato bo'lsa — brauzerdagi nusxadan.
- Sahifada ikki forma: «Bajarildi deb belgilash» va «O'chirish…» — ikkalasi bitta
actionga. - Topilmasa —
errorElement(VazifaXatosi), menyu joyida qoladi. /vazifa/02→/vazifa/2endi loader'da (replace()).
git switch -c feat/vazifa-loader8.2 Loader: vazifa-marshruti.ts
Yangi fayl. Asosiy qismi (kanondan aynan, izohlar telefon uchun qayta bo'lingan):
// vazifa-marshruti.ts — "/vazifa/:id" ning ma'lumot qismi (19/#05,
// data mode). loader — sahifa chizilishidan OLDIN ma'lumotni oladi
// (router kutadi), action — <Form method="post"> yuborilganda
// ishlaydi. Ikkalasi ham React'dan tashqarida: hook yo'q, so'rov —
// request.signal bilan (foydalanuvchi boshqa sahifaga o'tsa, router
// so'rovni bekor qiladi)
import { data, redirect, replace } from "react-router";
const keshdagi = (): Vazifalar => yukla() ?? boshlangichVazifalar;
// Haqiqat manbai — server; u javob bermasa (oflayn, xato) yoki bu
// kalitni hali bilmasa (versiya 0) — shu brauzerdagi nusxa. Bekor
// qilingan so'rov — qayta tashlanadi: router uni o'zi kutadi
async function royxatniOl(signal: AbortSignal): Promise<Vazifalar> {
if (!navigator.onLine) {
return keshdagi();
}
try {
const javob = serverRoyxatiniTekshir(await api.royxat({ signal }));
return javob.versiya === 0 ? keshdagi() : javob.vazifalar;
} catch (xato) {
if (xato instanceof ApiXatosi && xato.kod === "BEKOR") {
throw xato;
}
console.warn(`Vazifa keshdan: ${xatoMatni(xato)}`);
return keshdagi();
}
}
/** "abc", "1.5" — vazifa yo'q (404); "02" — "2" ga
* yo'naltiriladi */
function raqaminiOl(id: string | undefined): number {
const raqam = Number(id);
if (!Number.isInteger(raqam) || raqam < 1) {
// data() — xato JAVOB (status bilan): errorElement uni
// isRouteErrorResponse bilan taniydi
throw data(`«${id ?? ""}» raqamli vazifa yo'q.`, { status: 404 });
}
if (id !== String(raqam)) {
// Bir vazifa — bitta manzil (19/#04 dagi <Navigate replace>
// o'rni). replace() — redirect() ning tarixga yozuv
// QO'SHMAYDIGAN turi
throw replace(`/vazifa/${raqam}`);
}
return raqam;
}
export async function vazifaLoader({
params,
request,
}: LoaderFunctionArgs): Promise<VazifaMalumoti> {
const raqam = raqaminiOl(params.id);
const vazifa = (await royxatniOl(request.signal)).find(
(v) => v.id === raqam,
);
if (vazifa === undefined) {
throw data(`«${raqam}» raqamli vazifa yo'q.`, { status: 404 });
}
return vazifa;
}Darsdagi g'oyalar shu yerda: params.id tekshiriladi va noto'g'ri bo'lsa throw data(…, { status: 404 }); request.signal API qatlamiga uzatiladi (bekor qilish xatosi "BEKOR" yutilmaydi — router uni o'zi taniydi); server javob bermasa — yiqilmaydi, brauzerdagi nusxa; eski manzil — throw replace(…).
Diqqat: Kanonning birinchi variantida bu yerda
redirect()edi. Oldingi darsdagi «Orqaga» tuzatishining yangi e2e tekshiruvi buni topdi:/vazifa/02ochilganda tarix +1 bo'ldi. «Orqaga»/vazifa/02ga qaytdi, loader yana/vazifa/2ga otdi — aylana (natijalar/04-orqaga-tekshiruv.txt: "e2e-19 37 2 ").replace()ga almashtirildi. Yo'naltirish deyarli doimreplacebilan — oldingi darsdagi qoida loader'da ham kuchda.
Komponent endi ancha qisqa. VazifaSahifasi.tsx dan useParams, yukla(), "topilmadi" tarmog'i va <Navigate> olib tashlandi. O'rniga bitta qator:
// typeof vazifaLoader — tur loader'ning qaytargan qiymatidan
const vazifa = useLoaderData<typeof vazifaLoader>();useLoaderData<typeof vazifaLoader>() — TypeScript turni loader'ning qaytish turidan oladi: vazifa — VazifaMalumoti, qo'lda yozish shart emas. Loader o'zgarsa, tur ham o'zgaradi.
8.3 Action: belgilash va o'chirish
Xuddi o'sha faylda (kanondan aynan):
export async function vazifaAction({
params,
request,
}: ActionFunctionArgs): Promise<VazifaAmalNatijasi | Response> {
const raqam = raqaminiOl(params.id);
const forma = await request.formData();
const niyat = forma.get("niyat");
const sozlama = { signal: request.signal };
try {
if (niyat === "ochir") {
await api.ochir(raqam, sozlama);
keshniYangila((eski) => eski.filter((v) => v.id !== raqam));
return redirect("/");
}
if (niyat === "almashtir") {
const bajarildi = forma.get("bajarildi") === "true";
const vazifa = serverVazifasiniTekshir(
await api.yangila(raqam, { bajarildi }, sozlama),
);
keshniYangila((eski) =>
eski.map((v) => (v.id === raqam ? vazifa : v)),
);
return {
ok: true,
xabar: bajarildi ? "Bajarildi deb belgilandi." : "Yana faol.",
};
}
} catch (xato) {
return { ok: false, xabar: `Saqlanmadi: ${xatoMatni(xato)}` };
}
throw data("Noma'lum amal", { status: 400 });
}niyat— «Bitta action — bir nechta amal» bo'limidagi naqsh.- O'chirilgach —
redirect("/"): vazifa yo'q, uning sahifasida qolish ma'nosiz. Bu yerda push to'g'ri — o'chirish yangi qadam. - Server rad etsa — tashlamaydi, qaytaradi:
{ ok: false, xabar }. Sahifa joyida qoladi, xabar ko'rinadi. keshniYangila— brauzerdagi nusxa va boshqa tablar (BroadcastChannel). Ro'yxat sahifasi ochilganda ham to'g'ri holat bo'lsin.
8.4 Sahifa: VazifaUmumiy.tsx da formalar
Index bo'limga ikki <Form> (kanondan, qisqartirilgan):
// Action natijasi (oxirgi yuborishniki): xabar yoki xato
const natija = useActionData<typeof vazifaAction>();
// Yuborilmoqda: tugma matni — router holatidan
const yuborilmoqda = useNavigation().state === "submitting";
const [soralmoqda, setSoralmoqda] = useState(false);
…
<Form method="post" className="qator">
<input type="hidden" name="niyat" value="almashtir" />
<input
type="hidden"
name="bajarildi"
value={String(!vazifa.bajarildi)}
/>
<Tugma type="submit" id="vazifa-almashtir" variant="asosiy">
{yuborilmoqda
? "Saqlanmoqda…"
: vazifa.bajarildi
? "Yana faol qilish"
: "Bajarildi deb belgilash"}
</Tugma>
</Form>
…
<p id="vazifa-xabar" role="status">
{natija?.xabar}
</p>«O'chirish…» birinchi bosilganda soralmoqda — true, «Ha, o'chirish» va «Bekor qilish» chiqadi. Ikkinchi forma niyat=ochir ni yuboradi. Natija xabari role="status" da — Malika eshitadi.
8.5 Marshrut va xato sahifasi
marshrutlar.tsx (kanon):
path: "/",
element: <Qobiq />,
errorElement: <XatoSahifasi />,
+ // 19/#05: sahifa birinchi ochilishida loader ishlayotgan
+ // bo'lsa (/vazifa/2 to'g'ridan) — router shuni ko'rsatadi.
+ // Ro'yxat sahifasida loader yo'q: u birinchi kadrda
+ // chiziladi (CLS 0, 13/#30)
+ hydrateFallbackElement: <p role="status">Yuklanmoqda…</p>,
...
{
path: "vazifa/:id",
element: <VazifaSahifasi />,
- children: [{ index: true, element: <VazifaUmumiy /> }],
+ loader: vazifaLoader,
+ errorElement: <VazifaXatosi />,
+ children: [
+ {
+ index: true,
+ element: <VazifaUmumiy />,
+ action: vazifaAction,
+ },
+ ],
},Nega action index bolada, otada emas? <Form> ga action atributi berilmasa, u o'zi turgan marshrutga yuboradi. Formalar VazifaUmumiy da — index bolada. Action otada bo'lsa, router "bu marshrutda action yo'q" deb 405 xatosini berardi.
VazifaXatosi.tsx — «Loader xato tashlasa» bo'limidagi DishError ning o'zi: useRouteError(), 404 bo'lsa sarlavha "Vazifa topilmadi", boshqa xatoda — "Vazifani ochib bo'lmadi". Matn javob-xatoda String(xato.data) dan, kutilmagan xatoda — xatoMatni(xato) (Error ning message i). Pastda «Ro'yxatga qaytish» havolasi. U vazifa/:id marshrutida — xato faqat sahifa qismini egallaydi, Qobiq (menyu, sarlavha) joyida qoladi.
8.6 ESLint: throw faqat Error emas
Birinchi npm run lint to'rtta xato berdi (natijalar/tekshiruv-05.txt):
59:11 error Expected an error object to be thrown @typescript-eslint/only-throw-errorTarjimasi: "Xato obyekti tashlanishi kutilgan edi". Bu qoida ESLint plaginlari dan — odatda foydali: throw "matn" stek izini yo'qotadi. Lekin router aynan throw data(…) va throw replace(…) ni kutadi. Kanon qoidani o'chirmadi, eslint.config.js da faqat ikki turga ruxsat berdi: allow: [{ from: "lib", name: "Response" }, { from: "package", package: "react-router", name: "DataWithResponseInit" }]. replace() va redirect() — Response, data() — DataWithResponseInit. Boshqa narsani tashlash hamon xato.
8.7 Tekshiruvlar
git add -A
git commitCommit (kanon): feat: vazifa sahifasi — loader, action (belgilash, o'chirish), errorElement.
npm test— node:test 182, Vitest 55 (+6: loader, 404,/vazifa/02, keshdan, action, server 500). Testda ham xotiradagi router:createMemoryRouterva soxtafetch.lint,format:check,tip,knip—exit 0. Build:gzip: 114.49 kB(oldin 114.25).- Brauzer: 159/159, 72/72, 19-qism — 39/39. Yangi sakkiztasidan uchtasi:
✅ loader: to'g'ridan ochilganda serverdan (GET): ["Non olish #bozor",["GET"]]
✅ action: belgilandi — PATCH, keyin loader qayta (GET), sahifa yangi: [["GET","PATCH 1","GET"],"Bajarilgan","Bajarildi deb belgilandi."]
✅ errorElement: o'chirilgan vazifa — 404, menyu joyida: ["Vazifa topilmadi",3]Ikkinchi qatorga qarang: GET, PATCH 1, GET. Bu — «Forma router'ga yuboriladi» bo'limidagi uch qatorli konsolning haqiqiy serverdagi ko'rinishi: loader, action, revalidatsiya. Uchinchisi: o'chirilgan vazifaning sahifasi 404, menyuda esa uch bo'lim (3) turibdi — xato faqat sahifa qismida. Diff: 7 fayl, +446 −50.
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
No \HydrateFallback` element provided…`, ekran bo'sh |
birinchi ochilishda loader ishlayapti | hydrateFallbackElement |
useLoaderData() — undefined |
loader boshqa marshrutda (otada) | o'z marshrutiga loader yoki useRouteLoaderData(id) |
| action'dan keyin ro'yxat yangilanmadi | qo'lda setState kutildi |
loader serverdan olsin — revalidatsiya o'zi yangilaydi |
| forma yuborilganda 405 | <Form> turgan marshrutda action yo'q |
action'ni shu marshrutga yoki <Form action="…"> |
| xato butun sahifani (menyuni ham) yutdi | errorElement faqat ildizda |
errorElement ni sahifa marshrutiga |
| "Unexpected Application Error!" | hech qayerda errorElement yo'q |
ildiz marshrutga o'z xato sahifasi |
| «Orqaga» aylanaga tushdi | loader'da redirect (push) |
replace() |
| yoqtirish manzilni o'zgartirdi | <Form> ishlatildi |
useFetcher |
useNavigate ni action ichida chaqirish ham ko'p uchraydi: Invalid hook call. Action — oddiy funksiya, komponent emas. O'tish kerak bo'lsa — return redirect(…).
10. Mashqlar
Mashqlar kurs/mashqlar/19/05-data-mode/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): kim, qachon?
Bo'sh joylarni to'ldiring:
- Sahifa uchun ma'lumot olish — marshrutning funksiyasida.
- Komponent bu ma'lumotni hook'i bilan oladi.
<Form method="post">yuborilganda router marshrutning funksiyasini chaqiradi.- Action tugagach router loader'larni qayta chaqiradi. Bu deyiladi.
- Sahifadan ketmasdan amal bajarish uchun ishlatiladi.
Yechim
loader, useLoaderData, action, revalidatsiya, useFetcher. Eslab qolish uchun: loader — o'qish (GET), action — yozish (POST, PATCH, DELETE). HTTP metodlari kabi: <Form method="get"> loader'ga, method="post" action'ga boradi. Fetcher — xuddi shular, lekin navigatsiyasiz.
2-mashq (o'rta): effektdan loader'ga
Quyidagi sahifa narxni useEffect da oladi. Uni loader'ga o'tkazing:
useStatevauseEffectolib tashlansin.- Marshrutga
loader—getPrice()ni qaytaradi. - Birinchi ochilishda "Yuklanmoqda…" ko'rinsin (
hydrateFallbackElement).
Ishora: «Birinchi loader» va «Birinchi ochilish» bo'limlari.
import { useEffect, useState } from "react";
import { createMemoryRouter, RouterProvider } from "react-router";
const getPrice = () =>
new Promise((resolve) => setTimeout(() => resolve(35000), 250));
function Price() {
const [price, setPrice] = useState(null);
useEffect(() => {
getPrice().then(setPrice);
}, []);
if (price === null) return <p>Yuklanmoqda…</p>;
return <p>Osh: {price} so'm</p>;
}
const router = createMemoryRouter([
{ path: "/", element: <Price /> },
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
Osh: 35000 so'mYechim
import {
createMemoryRouter,
RouterProvider,
useLoaderData,
} from "react-router";
const getPrice = () =>
new Promise((resolve) => setTimeout(() => resolve(35000), 250));
function Price() {
const price = useLoaderData();
return <p>Osh: {price} so'm</p>;
}
const router = createMemoryRouter([
{
path: "/",
element: <Price />,
loader: getPrice,
hydrateFallbackElement: <p>Yuklanmoqda…</p>,
},
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
Osh: 35000 so'mPrice endi bir qator: ma'lumot doim tayyor. loader: getPrice — funksiyaning o'zi berildi, chaqiruvi emas (getPrice() emas). Router uni o'zi, kerakli paytda chaqiradi. "Yuklanmoqda…" endi komponentning ishi emas — marshrut darajasida.
3-mashq (qiyin): stol band qilish
/ sahifasi — stollar ro'yxati va band qilish formasi:
- Loader
tablesmassivini qaytaradi; ekrandaBand: 1 ta. - Forma:
name="guest"input (aria-label «Mehmon») va «Band qilish» tugmasi. - Action: ism bo'sh bo'lsa —
data({ error: "Ism kiriting" }, { status: 400 }); aks holdatables.push(guest)varedirect("/rahmat"). /rahmatsahifasi: «Rahmat! Stol band qilindi.»
Ishora: «Xato qaytarish» va «Amaldan keyin boshqa sahifa» bo'limlari. Avval bo'sh yuborib xatoni, keyin ism bilan yuborib sinang.
import {
createMemoryRouter,
data,
Form,
redirect,
RouterProvider,
useActionData,
useLoaderData,
} from "react-router";
const tables = ["Ali"];
// Booking, loader, action — shu yerga
const router = createMemoryRouter([
{ path: "/", element: <p>Stollar</p> },
{ path: "/rahmat", element: <p>Rahmat!</p> },
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
StollarYechim
import {
createMemoryRouter,
data,
Form,
redirect,
RouterProvider,
useActionData,
useLoaderData,
} from "react-router";
const tables = ["Ali"];
function Booking() {
const list = useLoaderData();
const result = useActionData();
return (
<Form method="post">
<p>Band: {list.length} ta</p>
<input name="guest" aria-label="Mehmon" />
<button>Band qilish</button>
{result?.error && <p role="alert">{result.error}</p>}
</Form>
);
}
const router = createMemoryRouter([
{
path: "/",
element: <Booking />,
loader: () => [...tables],
action: async ({ request }) => {
const guest = String((await request.formData()).get("guest"));
if (guest.trim() === "") {
return data({ error: "Ism kiriting" }, { status: 400 });
}
tables.push(guest.trim());
return redirect("/rahmat");
},
},
{ path: "/rahmat", element: <p>Rahmat! Stol band qilindi.</p> },
]);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
Rahmat! Stol band qilindi.Harakat qatori ikki holatni tekshiradi: bo'sh yuborilganda "Ism kiriting" (ekran qadami), ism bilan — tasdiq sahifasi. String(…) — formData.get maydon bo'lmasa null qaytaradi, String(null).trim() yiqilmaydi. loader: () => [...tables] — nusxa: React yangi massivni ko'rib, qayta chizadi. Xato holatida status 400 — loader keraksiz qayta chaqirilmaydi.
4-mashq: Vazifalar qadami
feat/vazifa-loader branch'ida «Vazifalar qadami» bo'limidagi o'zgarishlarni qiling: vazifa-marshruti.ts (loader, action), VazifaSahifasi.tsx — useLoaderData, VazifaUmumiy.tsx — ikki forma, VazifaXatosi.tsx, marshrutlar.tsx va eslint.config.js. Keyin commit.
Yechim
Tekshiruv ro'yxati (npm run dev, «Bahor» API ishlab turgan holda):
/vazifa/1ni yangi tabda oching — bir zum "Yuklanmoqda…", keyin vazifa. DevTools Network'da bitta GET.- «Bajarildi deb belgilash» — tugma "Saqlanmoqda…", keyin "Yana faol qilish" va "Bajarildi deb belgilandi.". Network: PATCH, keyin GET.
- «O'chirish…» → «Ha, o'chirish» — ro'yxat sahifasi, vazifa yo'q. «Orqaga» — "Vazifa topilmadi", menyu joyida.
/vazifa/02— manzil/vazifa/2, brauzerning «Orqaga» tugmasi aylanaga tushmaydi.- Network'da "Offline" ni yoqib,
/vazifa/2ni yangilang — vazifa keshdan, konsoldaVazifa keshdan: …. npm test— node 182, Vitest 55;npm run lint— xatosiz.
11. Real ishda
- Remix va React Router framework mode — loader va action g'oyasi aynan shu yerdan chiqqan. Keyingi darsda ko'ramiz.
- Next.js — server komponentlar va Server Actions: ma'lumot ham, o'zgartirish ham marshrutga bog'langan. Data mode'ni bilgan odam ularni tez tushunadi.
- TanStack Query bilan birga — katta ilovalarda loader keshni to'ldiradi, komponent esa keshdan o'qiydi.
vazifalarda shunday bo'ladi: TanStack Query I. - Intervyu savollari: "loader va
useEffectbilan yuklash farqi?", "revalidatsiya nima?", "<Form>vauseFetcherqachon?", "errorElementqayerga qo'yiladi?".
Xulosa
loader— sahifa chizilishidan oldin ma'lumot; komponentuseLoaderData()bilan oladi. Ichma-ich loader'lar parallel — zinapoya yo'q.- Kutish holati —
useNavigation().state(butun ilova), birinchi ochilishda —hydrateFallbackElement. <Form method="post">→action→ revalidatsiya. Xato —useActionData()ga qaytariladi; 4xx/5xx status loader'larni qayta chaqirmaydi.redirect— push,replace— almashtirish; action va loader'dauseNavigateo'rniga.useFetcher— navigatsiyasiz amal: o'zstate,data,formData(optimistik UI).- Xato —
throw data(…, { status }); uni eng yaqinerrorElementuseRouteErrorvaisRouteErrorResponsebilan ko'rsatadi.errorElement— iloji boricha pastda.
Keyingi dars: React Router V: framework mode — data mode ustidagi Vite plagini: fayl asosidagi marshrutlar, avtomatik tiplar va SSR. vazifalar da sinab, o'lchaymiz.
Manbalar
- React Router: Data Loading, Actions, Pending UI — reactrouter.com/start/data/… (2026-10 holatiga, v8.4.0)
- React Router:
useFetcher,useNavigation,useRouteError,isRouteErrorResponse,data,redirect,replace— reactrouter.com/api/… - React Router: Route Object (
loader,action,errorElement,hydrateFallbackElement,lazy) — reactrouter.com/start/data/route-object - typescript-eslint:
only-throw-error(allow) — typescript-eslint.io/rules/only-throw-error
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!