IlmHamroh
JavaScript Full-stack/19-qism. React: routing, server state, formalar3/36-dars30 daqiqa
Mundarija (35)

React Router dinamik marshrutlar: useParams, ichma-ich marshrutlar, Outlet va layout

Qisqacha: path: "/taom/:slug" — bitta marshrut yuzlab sahifaga xizmat qiladi: :slug o'rniga manzildagi istalgan qiymat tushadi, komponent uni useParams() bilan o'qiydi (doim satr). Marshrutlar ichma-ich yoziladi (children). Ota marshrut umumiy qismni chizadi, bolasi esa otaning <Outlet /> turgan joyida chiqadi. index: true — ota manzilining o'zi, "*" — boshqa hech biriga mos kelmagan manzil. Yo'li yo'q marshrut (layout) manzilga hech narsa qo'shmaydi, faqat umumiy ramka beradi.

Bu darsda

  • :slug va :id kabi dinamik segment yozasiz va qiymatini useParams bilan o'qiysiz. Parametr nega doim satr ekanini tushunasiz.
  • Statik, dinamik, ixtiyoriy (?) va * segmentlardan qaysi biri "yutishini" bilasiz.
  • Ichma-ich marshrutlar, <Outlet /> va index marshrut bilan sahifa ichida bo'limlar qurasiz.
  • Layout marshrutni (yo'lli va yo'lsiz) ajratasiz va <Outlet context> orqali bolalarga ma'lumot berasiz.
  • vazifalar ga /vazifa/:id sahifasini qo'shasiz: ro'yxatdagi har qatorda «Batafsil».

Oldin bilishingiz kerak: React Router I: sozlash, Route va Link, SPA routing qanday ishlaydi, State'ning saqlanishi va key bilan reset, Context I.

1. Nega bu kerak?

Oldingi darsda vazifalar ga uchta sahifa qo'shdik: ro'yxat, statistika va sozlamalar. Har biri — marshrutlar.tsx da bitta qator. Uchta sahifaga uchta qator — muammo yo'q.

Endi Jasur aka «Bahor» uchun yangi talab qo'ydi. Menyudagi 30 ta taomning har biri o'z sahifasiga ega bo'lsin, .../taom/osh havolasi osh sahifasini ochsin. Nima, 30 ta marshrut va 30 ta deyarli bir xil komponent yozamizmi?

Ikkinchi talab: taom sahifasida ikki bo'lim — «Tarkibi» va «Sharhlar». Sarlavha va narx tepada qoladi, faqat pastki qism almashadi.

Hayotiy o'xshatish: restoranning stol raqamlari. Ofitsiant har stol uchun alohida qoida yodlamaydi. U bitta qolipni biladi: "Stol № ___ ga choy olib bor". Raqam o'zgaradi, ish bir xil. Dinamik marshrut — ana shu qolip. Ikkinchi talab uchun esa rasm ramkasini eslang. Ramka devorda qoladi, ichidagi rasm almashadi. Layout — ramka, <Outlet /> — rasm turadigan joy.

2. Dinamik segment va useParams

2.1 Bitta marshrut — ko'p sahifa

Segment — manzilning ikki / orasidagi bo'lagi: /taom/osh da ikkita segment bor, taom va osh. Dinamik segment (dynamic segment) — ikki nuqta bilan boshlanadigan segment: :slug. U "shu joyga istalgan qiymat tushishi mumkin, uni slug deb ata" degani. /taom/osh, /taom/manti va /taom/lagmon — uchalasi ham /taom/:slug ga mos keladi.

Komponent qiymatni useParams() hook'i bilan o'qiydi. U manzildagi parametrlarni obyekt qilib qaytaradi: { slug: "manti" }.

Bir eslatma. Saytdagi natija oynasida manzil satri yo'q. Shuning uchun oldingi darsdagidek misollarda xotiradagi router (createMemoryRouter) ishlatamiz. Haqiqiy loyihada — createBrowserRouter, qolgan kod bir xil.

jsx
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";

const DISHES = {
  osh: { name: "Osh", price: "35 000 so'm" },
  lagmon: { name: "Lag'mon", price: "28 000 so'm" },
  manti: { name: "Manti", price: "30 000 so'm" },
};

function Menu() {
  return (
    <ul>
      {Object.entries(DISHES).map(([slug, dish]) => (
        <li key={slug}>
          <Link to={`/taom/${slug}`}>{dish.name}</Link>
        </li>
      ))}
    </ul>
  );
}

function Dish() {
  const { slug } = useParams();
  const dish = DISHES[slug];
  return (
    <section>
      <h2>{dish.name}</h2>
      <p>Narxi: {dish.price}</p>
      <Link to="/">Menyuga qaytish</Link>
    </section>
  );
}

const router = createMemoryRouter([
  { path: "/", element: <Menu /> },
  { path: "/taom/:slug", element: <Dish /> },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda («Manti» bosilgach):

text
Manti
Narxi: 30 000 so'm
Menyuga qaytish

«Ishga tushir» ni bosing va uchala taomni ham ochib ko'ring. Dish — bitta komponent, marshrut ham bitta. Menyuga to'rtinchi taom qo'shsangiz, DISHES ga bitta qator yozasiz, xolos — marshrutga tegmaysiz.

Qatorma-qator nima bo'ldi:

Qadam Manzil useParams() Ekranda
ochilish / {} menyu
«Manti» bosildi /taom/manti { slug: "manti" } Manti, 30 000 so'm
«Menyuga qaytish» / {} menyu

Slug — manzilda ishlatiladigan qisqa, o'qiladigan nom: lagmon, kok-choy. Raqam (/taom/12) o'rniga slug ishlatilsa, havola odamga ham, Google'ga ham tushunarli bo'ladi. Nomlash sizning qo'lingizda: :slug, :id, :taomId — useParams obyektidagi kalit aynan shu nom bo'ladi.

2.2 Parametr doim satr

«Bahor» da buyurtmalar raqamlangan: /buyurtma/12. Raqam bo'lgani uchun Sardor uni son deb o'yladi va === 12 bilan solishtirdi. Natija — "topilmadi". Sababini konsol ko'rsatadi. useLocation() — joriy manzilni beradigan hook, uni keyingi darsda to'liq ochamiz. Hozir u faqat "manzil satri" o'rnida ekranga yozadi:

jsx
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useLocation,
  useParams,
} from "react-router";

const ORDERS = [
  { id: 12, table: 4, total: "63 000 so'm" },
  { id: 13, table: 7, total: "35 000 so'm" },
];

function Address() {
  const { pathname } = useLocation();
  return <p>Manzil: {pathname}</p>;
}

function Order() {
  const { id } = useParams();
  console.log(typeof id, id);
  const number = Number(id);
  const order = Number.isInteger(number)
    ? ORDERS.find((o) => o.id === number)
    : undefined;
  return (
    <section>
      <Address />
      {order === undefined ? (
        <p>«{id}» raqamli buyurtma yo'q.</p>
      ) : (
        <p>
          {order.table}-stol, jami {order.total}
        </p>
      )}
      <Link to="/buyurtma/abc">Noto'g'ri havola</Link>
    </section>
  );
}

const router = createMemoryRouter(
  [{ path: "/buyurtma/:id", element: <Order /> }],
  { initialEntries: ["/buyurtma/12"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
string 12
string abc

Ekranda (havola bosilgach):

text
Manzil: /buyurtma/abc
«abc» raqamli buyurtma yo'q.
Noto'g'ri havola

initialEntries — xotiradagi router qaysi manzildan boshlashi. Birinchi chizishda manzil /buyurtma/12 edi va ekranda "4-stol, jami 63 000 so'm" chiqdi. Konsolning birinchi qatoriga qarang: string 12. Manzil — matn, router undan son yasamaydi. Shuning uchun:

  1. Number(id) bilan songa aylantiramiz.
  2. Number.isInteger bilan tekshiramiz: "abc" dan NaN chiqadi, u butun son emas.
  3. Topilmasa — yiqilmaymiz, odamga tushunarli xabar chiqaramiz.

Uchinchi qadam muhim: eski havola, xato nusxalangan havola, o'chirilgan taom — hammasi :id ga tushadi. Birinchi misolda /taom/shorva ochilsa, dish.name — xato va oq ekran. Dinamik marshrutda "topilmadi" holati doim bo'lsin.

Diqqat: useParams() dagi har qiymat — string yoki undefined. TypeScript ham shunday deydi: Readonly<Params<string>>, ya'ni string | undefined. Son, sana yoki boshqa tur kerak bo'lsa — o'zingiz aylantirasiz va tekshirasiz.

2.3 Bir nechta parametr

Bitta yo'lda bir nechta dinamik segment bo'lishi mumkin — nomlari har xil bo'lsin:

jsx
{ path: "/bolim/:category/taom/:slug", element: <Dish /> }
// /bolim/issiq/taom/osh → useParams():
//   { category: "issiq", slug: "osh" }

Segment faqat ikki / orasidagi bo'lakni oladi: :slug hech qachon / ni o'z ichiga olmaydi. /bolim/issiq/taom/osh/eski bu marshrutga mos kelmaydi — oxirida ortiqcha segment bor.

2.4 Tuzoq: parametr o'zgarsa, state qoladi

Taom sahifasiga «Porsiya» hisoblagichi qo'shdik. Mehmon oshdan 3 porsiya tanladi, keyin «Manti» ga o'tdi:

jsx
import { useState } from "react";
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";

function Dish() {
  const { slug } = useParams();
  const [portions, setPortions] = useState(1);
  return (
    <section>
      <h2>{slug}</h2>
      <p>
        Porsiya: {portions}{" "}
        <button onClick={() => setPortions(portions + 1)}>+</button>
      </p>
      <Link to="/taom/manti">Manti</Link>
    </section>
  );
}

const router = createMemoryRouter(
  [{ path: "/taom/:slug", element: <Dish /> }],
  { initialEntries: ["/taom/osh"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
manti
Porsiya: 3 +
Manti

Manti sahifasi ochildi, lekin porsiya — 3! Sabab — State'ning saqlanishi va key bilan reset darsidagi qoida: daraxtning o'sha joyida o'sha Dish turibdi, faqat parametr o'zgardi. React uni qayta o'rnatmaydi, state saqlanadi.

Davosi o'sha darsdagidek — key. Parametrni kalit qilsangiz, har taom — alohida nusxa:

jsx
import { useState } from "react";
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";

function Dish() {
  const { slug } = useParams();
  return <DishCard key={slug} slug={slug} />;
}

function DishCard({ slug }) {
  const [portions, setPortions] = useState(1);
  return (
    <section>
      <h2>{slug}</h2>
      <p>
        Porsiya: {portions}{" "}
        <button onClick={() => setPortions(portions + 1)}>+</button>
      </p>
      <Link to="/taom/manti">Manti</Link>
    </section>
  );
}

const router = createMemoryRouter(
  [{ path: "/taom/:slug", element: <Dish /> }],
  { initialEntries: ["/taom/osh"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
manti
Porsiya: 1 +
Manti

slug o'zgardi — key o'zgardi — React eski DishCard ni olib tashlab, yangisini 1 porsiya bilan qo'ydi. Qoida: dinamik sahifada "shu yozuvga tegishli" state bo'lsa (forma qoralamasi, tanlov, ochiq panel), unga parametrni key qilib bering.

Tekshirib ko'ring: /buyurtma/012 ochilsa, birinchi misoldagi Order nima ko'rsatadi? useParams() dagi id nima bo'ladi?

Javob

id — "012" (satr, manzildagidek). Number("012") — 12, demak 12-buyurtma topiladi: "4-stol, jami 63 000 so'm". Ya'ni bitta buyurtmaning endi ikki manzili bor: /buyurtma/12 va /buyurtma/012. Bu SEO va havola ulashishda chalkashlik beradi. Keyingi darsda <Navigate replace> bilan "bir yozuv — bitta manzil" qilamiz — vazifalar da /vazifa/02 → /vazifa/2.

3. Qaysi marshrut yutadi: statik, dinamik, ixtiyoriy va *

3.1 Statik segment dinamikdan kuchli

Jasur aka xodimlar uchun "yangi taom qo'shish" sahifasini xohladi: /taom/yangi. Lekin /taom/:slug ham yangi ni qabul qiladi! Router qaysi birini tanlaydi? Sinab ko'ramiz. Ataylab dinamik marshrutni oldin yozdik:

jsx
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";

function Dish() {
  const { slug } = useParams();
  return <p>Taom sahifasi: {slug}</p>;
}

function NewDish() {
  return <p>Yangi taom qo'shish formasi</p>;
}

function Menu() {
  return (
    <p>
      <Link to="/taom/osh">Osh</Link>{" "}
      <Link to="/taom/yangi">Yangi taom</Link>
    </p>
  );
}

const router = createMemoryRouter([
  { path: "/", element: <Menu /> },
  { path: "/taom/:slug", element: <Dish /> },
  { path: "/taom/yangi", element: <NewDish /> },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
Yangi taom qo'shish formasi

Router marshrutlarni yozilgan tartibda sinamaydi — har biriga ball berib, eng aniq mosini tanlaydi:

Segment turi Misol Kuchi
statik yangi eng kuchli
dinamik :slug o'rtacha
hammasini ushlovchi * eng kuchsiz

Tartib haqida o'ylamaysiz. Lekin endi yangi slug'li taom sahifasi hech qachon ochilmaydi — slug yaratishda band so'zlarni (yangi, tahrir) taqiqlang.

Maslahat: Eski maqolada <Switch> va "aniq marshrutni yuqoriga yozing" maslahatini ko'rsangiz — bu React Router 5 davri ("birinchi mos kelgani yutadi"). 6–8 versiyalarda tartib ahamiyatsiz.

3.2 Ixtiyoriy segment: ?

«Bahor» menyusi ikki tilda: /menyu (o'zbekcha) va /menyu/en (inglizcha). Ikki marshrut yozish shart emas. Segment oxiriga ? qo'yilsa, u ixtiyoriy (optional) bo'ladi: bo'lsa ham, bo'lmasa ham mos keladi.

jsx
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";

const TITLES = { uz: "Bugungi menyu", en: "Today's menu" };

function Menu() {
  const { lang = "uz" } = useParams();
  console.log("lang:", lang);
  return (
    <section>
      <h2>{TITLES[lang] ?? TITLES.uz}</h2>
      <Link to="/menyu">O'zbekcha</Link>{" "}
      <Link to="/menyu/en">English</Link>
    </section>
  );
}

const router = createMemoryRouter(
  [{ path: "/menyu/:lang?", element: <Menu /> }],
  { initialEntries: ["/menyu"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
lang: uz
lang: en

Ekranda:

text
Today's menu
O'zbekcha English

/menyu da useParams() — {}, ya'ni lang umuman yo'q. Destrukturlashdagi = "uz" (Obyekt destructuring) aynan shu holat uchun. Statik segment ham ixtiyoriy bo'lishi mumkin: /menyu/bugun? — /menyu va /menyu/bugun ikkalasi ham.

3.3 * — hammasini ushlovchi

Hammasini ushlovchi (catch-all, splat) marshrut — path: "*". U boshqa hech biriga mos kelmagan har qanday manzilni oladi. Oldingi darsda vazifalar dagi 404 sahifasi aynan shunday qurilgan edi. Yulduzcha yo'lning oxirida ham bo'lishi mumkin: /fayl/* — /fayl/ bilan boshlangan hamma manzil. Qolgan qism useParams()["*"] da:

jsx
import {
  createMemoryRouter,
  RouterProvider,
  useParams,
} from "react-router";

function File() {
  const params = useParams();
  return <p>Fayl yo'li: {params["*"]}</p>;
}

function NotFound() {
  return <p>Bunday sahifa yo'q</p>;
}

const router = createMemoryRouter(
  [
    { path: "/fayl/*", element: <File /> },
    { path: "*", element: <NotFound /> },
  ],
  { initialEntries: ["/fayl/rasmlar/osh.webp"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
Fayl yo'li: rasmlar/osh.webp

Dinamik segmentdan farqi: * bir nechta segmentni, / lari bilan birga oladi. Fayl yo'li, hujjat bo'limlari (/qollanma/boshlash/ornatish) kabi chuqurligi noma'lum manzillar uchun qulay.

* umuman bo'lmasa-chi? Manzil hech biriga mos kelmasa, router o'zining standart xato sahifasini chiqaradi:

jsx
import { createMemoryRouter, RouterProvider } from "react-router";

const router = createMemoryRouter(
  [{ path: "/", element: <p>Bosh sahifa</p> }],
  { initialEntries: ["/eski-havola"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
Error handled by React Router default ErrorBoundary: ErrorResponseImpl {status: 404, statusText: 'Not Found', data: 'Error: No route matches URL "/eski-havola"', error: Error: No route matches URL "/eski-havola", internal: true}

Ekranda:

text
Unexpected Application Error!
404 Not Found
💿 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.

" Hey developer" qatorlari — dev rejimidagi maslahat: "o'z ErrorBoundary yoki errorElement ingizni bering". Production build'da faqat birinchi ikki qator qoladi. Mehmon uchun bu inglizcha, qo'rqitadigan matn. Xato sahifasi (errorElement) — React Router IV: data mode darsida. 404 uchun esa * marshruti yetarli: u xato emas, oddiy sahifa.

Tekshirib ko'ring: Marshrutlar: /taom/:slug, /taom/yangi, *. /taom/yangi/rasm manzili qaysi biriga tushadi?

Javob

* ga. /taom/yangi — aniq ikki segment, /taom/:slug ham ikki segment. Uchinchi segment (rasm) ularning hech biriga sig'maydi. Dinamik segment / dan o'tolmaydi. Qolgan yagona nomzod — *, u har qanday manzilni oladi.

4. Ichma-ich marshrutlar va <Outlet />

4.1 Bo'lim almashadi, sarlavha qoladi

Endi ikkinchi talab: /taom/osh (tarkibi) va /taom/osh/sharhlar. Sarlavha, narx va ikki havola ikkalasida bir xil.

Oldingi darsda children va <Outlet /> ni bir qavatda — vazifalar qobig'ida qisqa ko'rdik. Endi chuqurroq. Ichma-ich marshrut (nested route) — boshqa marshrutning children massivi ichidagi marshrut. Ota umumiy qismni chizadi, bola esa otaning <Outlet /> turgan joyida chiqadi — ramka ichidagi rasm o'rni. Endi ota o'zi ham dinamik (:slug):

jsx
import { useEffect } from "react";
import {
  createMemoryRouter,
  Link,
  Outlet,
  RouterProvider,
  useParams,
} from "react-router";

const DISHES = {
  osh: {
    name: "Osh",
    price: "35 000 so'm",
    parts: "guruch, sabzi, go'sht",
    reviews: 2,
  },
};

function Dish() {
  const { slug } = useParams();
  const dish = DISHES[slug];
  useEffect(() => console.log("Dish ekranga chiqdi"), []);
  return (
    <section>
      <h2>
        {dish.name} — {dish.price}
      </h2>
      <Link to={`/taom/${slug}`}>Tarkibi</Link>{" "}
      <Link to={`/taom/${slug}/sharhlar`}>Sharhlar</Link>
      <Outlet />
    </section>
  );
}

function Parts() {
  const { slug } = useParams();
  return <p>Tarkibi: {DISHES[slug].parts}</p>;
}

function Reviews() {
  const { slug } = useParams();
  return <p>Sharhlar: {DISHES[slug].reviews} ta</p>;
}

const router = createMemoryRouter(
  [
    {
      path: "/taom/:slug",
      element: <Dish />,
      children: [
        { index: true, element: <Parts /> },
        { path: "sharhlar", element: <Reviews /> },
      ],
    },
  ],
  { initialEntries: ["/taom/osh"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
Dish ekranga chiqdi

Ekranda («Sharhlar» bosilgach):

text
Osh — 35 000 so'm
Tarkibi Sharhlar
Sharhlar: 2 ta

Uch narsaga e'tibor bering:

  1. Bola yo'li nisbiy. path: "sharhlar" — / siz. Router uni otaning yo'liga qo'shadi: /taom/:slug + sharhlar = /taom/:slug/sharhlar.
  2. Bola ham parametrni ko'radi. Reviews ning o'z yo'lida :slug yo'q, lekin useParams() uni beradi. Parametrlar butun mos kelgan zanjir bo'ylab yig'iladi.
  3. Ota qayta o'rnatilmadi. «Sharhlar» bosilganda konsolga yangi qator chiqmadi. Dish joyida qoldi, faqat Outlet ichi almashdi. Ota state'i (masalan, porsiya) bo'lim almashganda yo'qolmaydi.

Marshrut daraxti va ekrandagi tuzilma bir-biriga mos. Chapda — marshrutlar, o'ngda — /taom/osh/sharhlar ochilganda ekranda nima ichida nima turishi:

flowchart LR
  subgraph M["Marshrutlar"]
    R1["/taom/:slug"] --> R2["index"]
    R1 --> R3["sharhlar"]
  end
  subgraph E["Ekranda: /taom/osh/sharhlar"]
    D["Dish: sarlavha, havolalar"] --> O["Outlet"]
    O --> S["Reviews"]
  end
  R3 -. "mos keldi" .-> S

4.2 Index marshrut — otaning "o'z" sahifasi

{ index: true, element: <Parts /> } — index marshrut (oldingi darsdagi ro'yxat sahifasi kabi). Yo'li yo'q: ota manzili aynan ochilganda (/taom/osh) Outlet ga tushadi. Index bo'lmasa, /taom/osh da Outlet bo'sh qoladi. Index marshrutda children bo'lmaydi — u daraxtning "bargi".

Diqqat: Bola yo'lini / bilan boshlamang. path: "/sharhlar" — mutlaq yo'l, router uni /taom/:slug ichida qabul qilmaydi va ilova umuman ishga tushmaydi. Xato matni «Ko'p uchraydigan xatolar» bo'limida.

4.3 Nechta qavat bo'lsa ham

Qavatlar ko'p bo'lishi mumkin: /taom/osh/sharhlar/7 → mos kelgan zanjir [qobiq, taom, sharhlar, sharh]. Har biri keyingisini o'z Outlet iga chizadi — matryoshka kabi.

Tekshirib ko'ring: Ichma-ich misolda Dish dagi <Outlet /> ni o'chirib tashlasak, /taom/osh/sharhlar ochilganda ekranda nima bo'ladi?

Javob

Faqat sarlavha va ikki havola. Router Reviews ni topdi, lekin chizadigan joy yo'q — xato ham, ogohlantirish ham chiqmaydi. "Bola ko'rinmayapti" — avval otada Outlet borligini tekshiring.

5. Layout marshrutlar

5.1 Ramka — butun ilova uchun

Sayt menyusi, "manzil satri" — hamma sahifada bir xil. Ularni bitta ota marshrutga chiqaramiz. Bu — oldingi darsdagi layout marshrut, vazifalar dagi Qobiq kabi: path: "/", menyu va <Outlet />, bolalari — index, boshqa sahifalar va *. Endi uni taom sahifasi bilan birga ko'ramiz:

jsx
import {
  createMemoryRouter,
  Link,
  Outlet,
  RouterProvider,
  useLocation,
  useParams,
} from "react-router";

function Shell() {
  const { pathname } = useLocation();
  return (
    <div>
      <p>
        «Bahor» | <Link to="/">Menyu</Link>{" "}
        <Link to="/ichimliklar">Ichimliklar</Link>
      </p>
      <p>Manzil: {pathname}</p>
      <Outlet />
    </div>
  );
}

function Menu() {
  return <Link to="/taom/osh">Osh</Link>;
}

function Dish() {
  return <p>Taom: {useParams().slug}</p>;
}

const router = createMemoryRouter([
  {
    path: "/",
    element: <Shell />,
    children: [
      { index: true, element: <Menu /> },
      { path: "taom/:slug", element: <Dish /> },
      {
        path: "ichimliklar",
        element: <p>Ko'k choy — 5 000 so'm</p>,
      },
      { path: "*", element: <p>Bunday sahifa yo'q</p> },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
«Bahor» | Menyu Ichimliklar
Manzil: /ichimliklar
Ko'k choy — 5 000 so'm

Shell bir marta yozildi va har sahifada bor — * (404) da ham: mehmon adashsa, menyu ko'rinib turadi.

5.2 Yo'lsiz layout — ramka bor, segment yo'q

Endi Jasur aka menyu va ichimliklar sahifalariga "Bugun 10% chegirma" bannerini qo'ymoqchi. Aloqa sahifasiga — yo'q. Manzillar o'zgarmasin: /menyu, /ichimliklar, /aloqa.

Banner uchun ota kerak. Lekin otaga yo'l bersak (/aksiya/menyu), manzillar o'zgaradi. Yechim — yo'lsiz layout marshrut (pathless layout route): element va children bor, path yo'q. U daraxtga qavat qo'shadi, manzilga esa hech narsa qo'shmaydi.

jsx
import {
  createMemoryRouter,
  Link,
  Outlet,
  RouterProvider,
} from "react-router";

function Shell() {
  return (
    <div>
      <p>
        <Link to="/menyu">Menyu</Link>{" "}
        <Link to="/ichimliklar">Ichimliklar</Link>{" "}
        <Link to="/aloqa">Aloqa</Link>
      </p>
      <Outlet />
    </div>
  );
}

function SaleBanner() {
  return (
    <section>
      <p>Bugun 10% chegirma!</p>
      <Outlet />
    </section>
  );
}

const router = createMemoryRouter(
  [
    {
      path: "/",
      element: <Shell />,
      children: [
        {
          element: <SaleBanner />,
          children: [
            { path: "menyu", element: <p>Osh, Manti</p> },
            { path: "ichimliklar", element: <p>Ko'k choy</p> },
          ],
        },
        { path: "aloqa", element: <p>+998 90 000 00 00</p> },
      ],
    },
  ],
  { initialEntries: ["/aloqa"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
Menyu Ichimliklar Aloqa
Bugun 10% chegirma!
Ko'k choy

/aloqa da banner yo'q edi, /ichimliklar da — bor, manzil esa shunchaki /ichimliklar. Yo'lsiz layout'ning ikki ishi:

  • umumiy ko'rinish — bir guruh sahifaga banner, yon panel yoki boshqa ramka;
  • umumiy shart — "bu sahifalarga faqat kirgan foydalanuvchi". Himoyalangan marshrutlar darsida vazifalar dagi Himoyalangan aynan yo'lsiz layout bo'ladi.
Marshrut path element Nima beradi
oddiy sahifa bor bor bitta sahifa
layout (yo'lli) bor bor + Outlet ramka va manzil qismi
layout (yo'lsiz) yo'q bor + Outlet faqat ramka
index yo'q (index: true) bor otaning o'z sahifasi

5.3 <Outlet context> — bolalarga ma'lumot uzatish

Ichma-ich misolda Parts va Reviews taomni qayta qidirdi: DISHES[slug]. Ota uni allaqachon topgan edi. Bolalar otadan to'g'ridan-to'g'ri olsa-chi? Props berib bo'lmaydi — <Outlet /> qaysi bola chizilishini o'zi hal qiladi.

Buning uchun Outlet da context prop'i bor. Bola uni useOutletContext() bilan o'qiydi. Bu — Context I darsidagi g'oyaning kichik varianti, faqat bitta qavat uchun.

Bitta nozik joy bor. Ichma-ich ikki layout bo'lsa, ichkisining konteksti tashqisinikini almashtiradi, birlashtirmaydi:

jsx
import {
  createMemoryRouter,
  Link,
  Outlet,
  RouterProvider,
  useOutletContext,
} from "react-router";

function Shell() {
  return <Outlet context={{ cafe: "Bahor" }} />;
}

function Dish() {
  const outer = useOutletContext();
  console.log("Dish oldi:", outer);
  return (
    <section>
      <Link to="/taom/osh/sharhlar">Sharhlar</Link>
      <Outlet context={{ dish: "Osh" }} />
    </section>
  );
}

function Reviews() {
  const inner = useOutletContext();
  console.log("Reviews oldi:", inner);
  return <p>{inner.dish} sharhlari ({inner.cafe})</p>;
}

const router = createMemoryRouter(
  [
    {
      path: "/",
      element: <Shell />,
      children: [
        {
          path: "taom/:slug",
          element: <Dish />,
          children: [{ path: "sharhlar", element: <Reviews /> }],
        },
      ],
    },
  ],
  { initialEntries: ["/taom/osh"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
Dish oldi: {cafe: 'Bahor'}
Reviews oldi: {dish: 'Osh'}

Ekranda:

text
Sharhlar
Osh sharhlari ()

Reviews faqat {dish: 'Osh'} ni oldi — cafe yo'qoldi, qavslar bo'sh. useOutletContext() eng yaqin ota Outlet ining kontekstini beradi. Bobosinikiga yetib bormaydi. Ikkalasi ham kerak bo'lsa, ota ularni o'zi birlashtiradi: context={{ ...outer, dish: "Osh" }}. Bugungi vazifalar qadamida aynan shu qilinadi.

Maslahat: useOutletContext() turini bilmaydi — TypeScript'da unknown. Kanonda shuning uchun kichik o'rovchi hook yoziladi: useQobiq() va useVazifa(). Ular to'g'ri turni qaytaradi va har sahifada useOutletContext<…>() deb yozish shart emas.

Tekshirib ko'ring: Yo'lsiz layout misolida /menyu ochilsa, SaleBanner necha marta ekranga chiqadi va manzil qanday bo'ladi?

Javob

Bir marta: zanjir — Shell → SaleBanner → "Osh, Manti". Manzil — /menyu. Yo'lsiz qavat manzilga hech narsa qo'shmaydi, chunki uning path i yo'q. Shuning uchun undagi bolalar yo'li to'g'ridan-to'g'ri Shell yo'liga (/) qo'shiladi.

6. Vazifalar qadami: vazifa sahifasi (holat 03)

6.1 Nima quramiz

vazifalar ro'yxatidagi har qatorga «Batafsil» havolasi. U /vazifa/2 kabi sahifani ochadi: sarlavhada vazifa matni, pastda holati (faol yoki bajarilgan), teglari va raqami. Sahifa o'zi layout: hozircha ichida bitta bo'lim — index. Keyingi qadamlarda unga tahrir (/vazifa/2/tahrir) qo'shiladi, shuning uchun Outlet hozirdan bor.

bash
git switch -c feat/vazifa-sahifasi

6.2 Marshrut: marshrutlar.tsx

Qobiq ning bolalariga bitta yozuv (kanon):

diff
+import { VazifaSahifasi } from "./sahifalar/VazifaSahifasi.tsx";
+import { VazifaUmumiy } from "./sahifalar/VazifaUmumiy.tsx";
...
       { path: "sozlamalar", element: <SozlamalarSahifasi /> },
+      // 19/#03: dinamik segment — ":id" har qanday qiymat
+      // (/vazifa/2, /vazifa/abc); ichida — o'z bolalari
+      // (ichma-ich)
+      {
+        path: "vazifa/:id",
+        element: <VazifaSahifasi />,
+        children: [{ index: true, element: <VazifaUmumiy /> }],
+      },
       // "*" — boshqa hech biriga mos kelmagan manzil
       { path: "*", element: <TopilmadiSahifasi /> },

Bugungi darsning uch g'oyasi bir yozuvda: dinamik segment (:id), ichma-ich marshrut (children) va index bola. Yo'l nisbiy — Qobiq ning / iga qo'shiladi.

6.3 Sahifa: VazifaSahifasi.tsx

Kod kanondan aynan, faqat izohlar telefonga sig'ishi uchun qisqa qatorlarga bo'lingan:

tsx
// VazifaSahifasi.tsx — "/vazifa/:id" (19/#03): bitta vazifa.
// Dinamik marshrut — :id manzildan olinadi (useParams), har
// vazifaga alohida komponent yozilmaydi. O'zi ham layout: ichidagi
// bo'limlar (ichma-ich marshrutlar) <Outlet /> o'rnida, vazifa
// ularga kontekst bilan beriladi
import { Link, Outlet, useParams } from "react-router";
import { boshlangichVazifalar } from "../holat.ts";
import { yukla } from "../saqlash.ts";
import { useQobiq } from "./qobiq-konteksti.ts";
import type { VazifaKonteksti } from "./vazifa-konteksti.ts";

export function VazifaSahifasi() {
  const { sarlavhaRef } = useQobiq();
  // Manzil parametri — doim satr (yoki undefined): "/vazifa/abc"
  // ham shu marshrutga tushadi, son ekanini o'zimiz tekshiramiz
  const { id } = useParams();
  const raqam = Number(id);
  // Ro'yxat — shu brauzerdagi nusxadan (19/#05 da loader
  // serverdan oladi)
  const vazifa = Number.isInteger(raqam)
    ? (yukla() ?? boshlangichVazifalar).find((v) => v.id === raqam)
    : undefined;
  if (vazifa === undefined) {
    return (
      <>
        <title>Vazifa topilmadi — Vazifalar</title>
        <h2 ref={sarlavhaRef} tabIndex={-1}>
          Vazifa topilmadi
        </h2>
        <p id="vazifa-yoq">{`«${id ?? ""}» raqamli vazifa yo'q.`}</p>
        <p>
          <Link to="/">Ro'yxatga qaytish</Link>
        </p>
      </>
    );
  }
  const kontekst: VazifaKonteksti = { sarlavhaRef, vazifa };
  return (
    <article id="vazifa" aria-labelledby="vazifa-sarlavha">
      <title>{`${vazifa.matn} — Vazifalar`}</title>
      <h2 id="vazifa-sarlavha" ref={sarlavhaRef} tabIndex={-1}>
        {vazifa.matn}
      </h2>
      <Outlet context={kontekst} />
      <p>
        <Link to="/">Ro'yxatga qaytish</Link>
      </p>
    </article>
  );
}

Bugun o'rganganingiz qatorma-qator:

  • useParams() → id — satr. Number(id) va Number.isInteger — «Parametr doim satr» bo'limidagi uch qadamning o'zi.
  • Topilmasa — yiqilmaydi: "Vazifa topilmadi" va ro'yxatga havola. /vazifa/99 ham, /vazifa/abc ham shu yerga tushadi.
  • Ro'yxat hozircha brauzerdagi nusxadan (yukla()). Serverdan olish — data mode darsida, loader bilan.
  • <title> — React 19 uni hujjat sarlavhasiga ko'chiradi.
  • sarlavhaRef — Qobiq dan (skip link uchun); bolalarga ham kerak, shuning uchun kontekstda qayta uzatiladi.

6.4 Kontekst: vazifa-konteksti.ts

Oxirgi nuqta — «<Outlet context>» bo'limidagi tuzoq. VazifaSahifasi ning Outlet konteksti Qobiq nikini almashtiradi. Agar unda faqat { vazifa } bo'lsa, bolalar sarlavhaRef ni yo'qotardi. Shuning uchun tur ikkalasining birlashmasi (kanon):

ts
// vazifa-konteksti.ts — VazifaSahifasi o'z bo'limlariga beradigan
// narsa (19/#03): Qobiq kontekstiga qo'shimcha — shu sahifadagi
// vazifa. Ichma-ich Outlet konteksti ota kontekstni almashtiradi
// (birlashtirmaydi) — shuning uchun sarlavhaRef ham shu yerda
import { useOutletContext } from "react-router";
import type { VazifaMalumoti } from "../turlar.ts";
import type { QobiqKonteksti } from "./qobiq-konteksti.ts";

export type VazifaKonteksti = QobiqKonteksti & {
  vazifa: VazifaMalumoti;
};

export function useVazifa(): VazifaKonteksti {
  return useOutletContext<VazifaKonteksti>();
}

QobiqKonteksti & { vazifa } — Kesishma darsidagi kesishma (intersection) turi: ikkala turning hamma maydonlari. Index bola — VazifaUmumiy.tsx — vazifani useVazifa() dan oladi va qayta qidirmaydi:

tsx
export function VazifaUmumiy() {
  const { vazifa } = useVazifa();
  const teglar = [...teglariniOl(vazifa.matn)];
  return (
    <dl className="vazifa-malumoti">
      <dt>Holati</dt>
      <dd id="vazifa-holati">
        {vazifa.bajarildi ? "Bajarilgan" : "Faol"}
      </dd>
      <dt>Teglar</dt>
      <dd id="vazifa-teglari">
        {teglar.length === 0 ? "yo'q" : teglar.join(", ")}
      </dd>
      <dt>Raqami</dt>
      <dd>{vazifa.id}</dd>
    </dl>
  );
}

teglariniOl — ro'yxatdagi teglarni ajratadigan funksiya (royxat.ts, Suspense darsida eksport qilingan). <dl> — "atama — qiymat" juftliklari uchun HTML ro'yxati, ekran o'quvchi ham shunday o'qiydi.

6.5 Qatorda havola: OddiyQator.tsx

Har qator oxiriga <Link> (kanon):

diff
+import { Link } from "react-router";
...
         onClick={() => onOchir(vazifa.id)}
       />
+      {/* 19/#03: vazifa sahifasi. Link — sahifani qayta
+          yuklamaydi (router manzilni o'zgartiradi); qatorning
+          oxirida — Tab tartibi (katak, tahrir, o'chirish)
+          o'zgarmaydi */}
+      <Link
+        to={`/vazifa/${vazifa.id}`}
+        className="batafsil"
+        aria-label={`Batafsil: ${vazifa.matn}`}
+      >
+        Batafsil
+      </Link>
     </Qator>

Nega qator oxirida? Bobur (faqat klaviatura) qatorlar bo'ylab Tab bilan yuradi: katak, tahrir, o'chirish. Havola o'rtaga qo'yilsa, uning o'rgangan tartibi buziladi. aria-label — Malika (ekran o'quvchi) uchun: o'nta "Batafsil" ni bir-biridan ajratib bo'lmaydi, "Batafsil: Non olish #bozor" esa aniq.

vazifalar.css ga 12 qator: havola rangi va joyi, .vazifa-malumoti — ikki ustunli grid.

6.6 Testlar va tekshiruvlar

Qatorda endi <Link> bor, u router'siz ishlamaydi — Vitest testlari komponentni xotiradagi router ichida chizadi (kanon, VazifaRoyxati.test.tsx):

diff
+// 19/#03: qatorda <Link> — router konteksti kerak (xotiradagi
+// router)
+import { MemoryRouter } from "react-router";
...
-  );
+    { wrapper: MemoryRouter },
+  );

MemoryRouter — oldingi darsdagi declarative mode'dagi xotira router'i. Yangi tekshiruv havola manzilini solishtiradi: getAttribute("href") — "/vazifa/1". Testlar chuqur — React Testing Library I darsida.

bash
git add -A
git commit

Commit (kanon): feat: /vazifa/:id — vazifa sahifasi, qatorda «Batafsil».

Tekshiruvlar (haqiqiy, holat 03):

  • npm test — node:test ℹ tests 174 · ℹ pass 174, Vitest Tests 49 passed (49). Son o'zgarmadi: mavjud testlar router ichiga olindi, yangi tekshiruv mavjud testga qo'shildi.
  • lint, format:check, tip, knip — exit 0. Build: index-….js 359.02 kB │ gzip: 113.68 kB (oldin 113.36 — sahifa +0.3 KB). Router allaqachon to'plamda, yangi sahifa deyarli bepul.
  • Brauzer: 13–17-qism tekshiruvlari 159/159, 18-qismniki 72/72, 19-qismniki 26/26. Yangi yettitasi — /vazifa/:id haqida:
text
✅ vazifa: «Batafsil» — sahifa (SPA), sarlavha, title: [true,"Non olish #bozor","Non olish #bozor — Vazifalar"]
✅ vazifa: holati va teglari (index bo'limi): ["Faol","#bozor","1"]
✅ vazifa: menyuda hech biri joriy emas: 0
✅ vazifa: to'g'ridan ochildi (/vazifa/2) — Bajarilgan: ["Sut olish #bozor","Bajarilgan"]
✅ vazifa: /vazifa/99 — «Vazifa topilmadi»: ["Vazifa topilmadi","«99» raqamli vazifa yo'q."]
✅ vazifa: /vazifa/abc — «Vazifa topilmadi»: ["Vazifa topilmadi","«abc» raqamli vazifa yo'q."]
✅ vazifa: CSP buzilishi yo'q: []

To'rtinchi qator: /vazifa/2 to'g'ridan ochildi — do'stga yuborilgan havola ham shunday ochiladi. Uchinchi qator: vazifa sahifasida menyudagi hech bir bo'lim "joriy" emas. Diff: 8 fayl, +139.

7. Ko'p uchraydigan xatolar

Eng ko'p uchraydigani — bola yo'lini / bilan boshlash. Ilova umuman ishga tushmaydi:

jsx
import {
  createMemoryRouter,
  Outlet,
  RouterProvider,
} from "react-router";

const router = createMemoryRouter([
  {
    path: "/taom/:slug",
    element: <Outlet />,
    children: [{ path: "/sharhlar", element: <p>Sharhlar</p> }],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}

Konsolda:

text
Uncaught Error: Absolute route path "/sharhlar" nested under path "/taom/:slug" is not valid. An absolute child route path must start with the combined path of all its parent routes.

Tarjimasi: "/taom/:slug ichidagi /sharhlar mutlaq yo'li noto'g'ri. Bolaning mutlaq yo'li barcha otalari yo'lining yig'indisi bilan boshlanishi kerak." Ya'ni ikki yo'l bor: path: "sharhlar" (nisbiy — tavsiya) yoki path: "/taom/:slug/sharhlar" (to'liq). Xato createMemoryRouter chaqirilgan joyda, marshrutlar o'qilayotganda chiqadi — komponent chizilishidan oldin.

Belgi Sabab Davosi
bola sahifa ko'rinmaydi, xato yo'q otada <Outlet /> yo'q ota elementiga <Outlet />
Cannot read properties of undefined (reading 'name') /taom/shorva — bunday taom yo'q, tekshiruv yo'q topilmadi holati: if (dish === undefined)
=== 12 hech qachon rost emas parametr — satr "12" Number(id) + Number.isInteger
boshqa taomga o'tganda eski state qoladi marshrut o'sha, komponent qayta o'rnatilmadi key={slug}
"Unexpected Application Error! 404 Not Found" * marshruti yo'q { path: "*", element: <NotFound /> }
useOutletContext() — undefined otaning Outlet ida context yo'q <Outlet context={…} />
bola otaning kontekstidagi maydonni ko'rmaydi ichki Outlet konteksti almashtirdi context={{ ...outer, yangi }}
Cannot destructure property 'basename' … <Link> router'dan tashqarida (ko'pincha testda) wrapper: MemoryRouter (React Router I dagi xato)

8. Mashqlar

Mashqlar kurs/mashqlar/19/03-marshrutlar/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): qaysi marshrut?

Marshrutlar (tartibi ataylab aralash):

  • A — /taom/:slug
  • B — /taom/yangi
  • C — /menyu/:lang?
  • D — *

Har manzil qaysi marshrutga tushadi? Harfini yozing.

  • /taom/manti →
  • /taom/yangi →
  • /menyu →
  • /menyu/en →
  • /taom →
  • /taom/osh/sharhlar →
Yechim
  • /taom/manti — A: ikki segment, ikkinchisi dinamikka tushadi.
  • /taom/yangi — B: statik segment dinamikdan kuchli, tartib ahamiyatsiz.
  • /menyu — C: :lang? ixtiyoriy, bo'lmasa ham mos keladi.
  • /menyu/en — C: lang — "en".
  • /taom — D: A va B ikki segment talab qiladi, :slug ixtiyoriy emas.
  • /taom/osh/sharhlar — D: uchinchi segmentga hech kim joy bermagan.

2-mashq (o'rta): ikki sahifani ichma-ich qiling

Quyidagi kodda taom sarlavhasi ikki komponentda takrorlangan. Uni bitta ota Dish ga chiqaring:

  1. /taom/:slug — ota, Dish sarlavha va <Outlet /> ni chizadi.
  2. Ota manzilining o'zida — narx (index marshrut).
  3. /taom/:slug/sharhlar — sharhlar (nisbiy yo'l "sharhlar").

Ekran o'zgarmasligi kerak. Ishora: «Ichma-ich marshrutlar va <Outlet />» bo'limidagi misol.

jsx
import {
  createMemoryRouter,
  Link,
  RouterProvider,
  useParams,
} from "react-router";

function Title() {
  const { slug } = useParams();
  return (
    <p>
      {slug}: <Link to={`/taom/${slug}`}>Narxi</Link>{" "}
      <Link to={`/taom/${slug}/sharhlar`}>Sharhlar</Link>
    </p>
  );
}

function Price() {
  return (
    <section>
      <Title />
      <p>35 000 so'm</p>
    </section>
  );
}

function Reviews() {
  return (
    <section>
      <Title />
      <p>2 ta sharh</p>
    </section>
  );
}

const router = createMemoryRouter(
  [
    { path: "/taom/:slug", element: <Price /> },
    { path: "/taom/:slug/sharhlar", element: <Reviews /> },
  ],
  { initialEntries: ["/taom/osh"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
osh: Narxi Sharhlar
2 ta sharh
Yechim
jsx
import {
  createMemoryRouter,
  Link,
  Outlet,
  RouterProvider,
  useParams,
} from "react-router";

function Dish() {
  const { slug } = useParams();
  return (
    <section>
      <p>
        {slug}: <Link to={`/taom/${slug}`}>Narxi</Link>{" "}
        <Link to={`/taom/${slug}/sharhlar`}>Sharhlar</Link>
      </p>
      <Outlet />
    </section>
  );
}

const router = createMemoryRouter(
  [
    {
      path: "/taom/:slug",
      element: <Dish />,
      children: [
        { index: true, element: <p>35 000 so'm</p> },
        { path: "sharhlar", element: <p>2 ta sharh</p> },
      ],
    },
  ],
  { initialEntries: ["/taom/osh"] },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda:

text
osh: Narxi Sharhlar
2 ta sharh

Title, Price va Reviews kerak bo'lmay qoldi: sarlavha bir joyda, bo'lim almashganda Dish (va uning state'i) joyida qoladi.

3-mashq (qiyin): taom topilmasa va kontekst

«Bahor» taom sahifasini yakunlang:

  1. /taom/:slug — Dish taomni DISHES dan topadi. Topilmasa — <p>«…» degan taom yo'q.</p> (nuqtalar o'rnida slug) va Outlet umuman chizilmaydi.
  2. Topilsa — sarlavha (<h2> da nomi) va <Outlet context={{ dish }} />.
  3. Index bola (Price) narxni useOutletContext() dan oladi: <p>Narxi: …</p>.
  4. Dish dan tashqarida * — <p>Bunday sahifa yo'q</p>.

DISHES — osh va manti (nom va narx). Router initialEntries: ["/taom/shorva", "/taom/manti", "/yoq"] bilan boshlansin — bu xotiradagi tarix, router sukut bo'yicha oxirgisidan (/yoq) boshlaydi. Har manzilni sinash uchun initialIndex: 0, 1, 2 qo'shing.

Yechim
jsx
import {
  createMemoryRouter,
  Outlet,
  RouterProvider,
  useOutletContext,
  useParams,
} from "react-router";

const DISHES = {
  osh: { name: "Osh", price: "35 000 so'm" },
  manti: { name: "Manti", price: "30 000 so'm" },
};

function Dish() {
  const { slug } = useParams();
  const dish = DISHES[slug];
  if (dish === undefined) {
    return <p>«{slug}» degan taom yo'q.</p>;
  }
  return (
    <section>
      <h2>{dish.name}</h2>
      <Outlet context={{ dish }} />
    </section>
  );
}

function Price() {
  const { dish } = useOutletContext();
  return <p>Narxi: {dish.price}</p>;
}

const router = createMemoryRouter(
  [
    {
      path: "/taom/:slug",
      element: <Dish />,
      children: [{ index: true, element: <Price /> }],
    },
    { path: "*", element: <p>Bunday sahifa yo'q</p> },
  ],
  {
    initialEntries: ["/taom/shorva", "/taom/manti", "/yoq"],
    initialIndex: 1,
  },
);

export default function App() {
  return <RouterProvider router={router} />;
}

Ekranda (initialIndex: 1 — /taom/manti):

text
Manti
Narxi: 30 000 so'm

Uchala manzil ham sinab ko'rildi:

initialIndex Manzil Ekranda
0 /taom/shorva «shorva» degan taom yo'q.
1 /taom/manti Manti, Narxi: 30 000 so'm
2 /yoq Bunday sahifa yo'q

/taom/shorva va /yoq farqiga qarang. Birinchisi marshrutga mos keldi (shakli to'g'ri), lekin bunday taom yo'q — buni faqat Dish biladi. Ikkinchisi hech qaysi marshrutga mos kelmadi — buni router biladi. Ikkalasiga ham "yo'q" sahifasi kerak, lekin ular har xil joyda tug'iladi. Price taomni qayta qidirmaydi: ota uni allaqachon topgan va tekshirgan.

4-mashq: Vazifalar qadami

feat/vazifa-sahifasi branch'ida «Vazifalar qadami» bo'limidagi o'zgarishlarni qiling: marshrutlar.tsx dagi yozuv, VazifaSahifasi.tsx, VazifaUmumiy.tsx, vazifa-konteksti.ts, OddiyQator.tsx dagi havola, CSS va ikki test faylidagi wrapper. Keyin commit.

Yechim

Tekshiruv ro'yxati:

  • npm test — node 174, Vitest 49. Link xatosi bilan yiqilsa — wrapper: MemoryRouter yo'q.
  • «Batafsil» — sahifa qayta yuklanmaydi, sarlavha va tabda vazifa matni.
  • /vazifa/abc va /vazifa/99 — "Vazifa topilmadi".
  • Qatorda Tab: katak, tahrir, o'chirish, keyin «Batafsil».

9. Real ishda

  • Internet-do'konlar — /product/123-qizil-ko'ylak: raqam bo'yicha qidiriladi, slug — SEO uchun.
  • Admin panellar — /admin/users/:id/orders/:orderId: har qavatning o'z layout'i va Outlet i.
  • Next.js — xuddi shu g'oyalar papka bilan: app/taom/[slug]/page.tsx — dinamik segment, layout.tsx — layout, (guruh) — yo'lsiz layout (Fayl-asosli routing, Layout va template). Bugun o'rganganlaringiz u yerga to'g'ridan-to'g'ri ko'chadi.
  • Ko'p uchraydigan bug — chatda boshqa suhbatga o'tganda yozilgan xabar qoladi: key={id} — eng tez tuzatish.
  • Intervyu savollari: "nested route va Outlet qanday ishlaydi?", "useParams nima qaytaradi va nega satr?", "layout route nima, yo'lsiz layout nimaga kerak?", "React Router qaysi marshrutni tanlashini qanday hal qiladi?".

Xulosa

  • :slug — dinamik segment: bitta marshrut ko'p sahifaga. Qiymat — useParams() dan, doim satr. Aylantiring, tekshiring, "topilmadi" holatini yozing.
  • Router eng aniq marshrutni tanlaydi: statik > dinamik > *. Tartib ahamiyatsiz. ? — ixtiyoriy segment.
  • Parametr o'zgarsa, komponent qayta o'rnatilmaydi — sahifaga tegishli state uchun key={slug}.
  • Ichma-ich marshrut — children: ota umumiy qismni chizadi, bola otaning <Outlet /> ida chiqadi. index — ota manzilining o'zi.
  • Layout — ramka. Yo'lsiz layout manzilga segment qo'shmaydi, faqat ramka yoki umumiy shart beradi.
  • <Outlet context> → useOutletContext() — eng yaqin otanikini oladi. Ichkisi tashqisini almashtiradi, kerak bo'lsa birlashtiring.

Keyingi dars: React Router III: dasturiy navigatsiya va URL holati — useNavigate bilan koddan o'tish, replace, useSearchParams, useLocation va nisbiy yo'llar; vazifalar da «Orqaga» tugmasi.

Manbalar

  • React Router: Routing (data mode) — reactrouter.com/start/data/routing (2026-10 holatiga, v8.4.0)
  • React Router: useParams, Outlet, useOutletContext — reactrouter.com/api/hooks/useParams, /api/components/Outlet, /api/hooks/useOutletContext
  • React Router: Route matching (ranking) — reactrouter.com/explanation (v8)
  • React: state'ni saqlash va reset — react.dev/learn/preserving-and-resetting-state
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
React Router dinamik marshrutlar: useParams, ichma-ich marshrutlar, Outlet va layout — IlmHamroh