Nested Routes — ichma-ich route lar, Outlet

~20 daq · React · Routing

Muammodan boshlaylik

Siz dashboard (boshqaruv paneli) yozyapsiz. Har bir sahifada yon menyu va yuqori sarlavha bir xil ko'rinadi. Faqat markaziy qism o'zgaradi:

┌─────────────────────────────────────────┐
│  Dashboard sarlavhasi        [Profil]   │  ← har doim
├──────────────┬──────────────────────────┤
│ Yon menyu    │   /dashboard/statistika  │
│              │                          │
│ Statistika   │   Bu yerda faqat         │  ← o'zgaradi
│ Foydalanuvch │   statistika ko'rinadi   │
│ Sozlamalar   │                          │
│              │                          │
└──────────────┴──────────────────────────┘

Har bir sahifa uchun alohida Route yozib, yon menyuni qayta-qayta chiqarsangiz — kodning takrorlanishi ko'payadi. Yangi sahifa qo'shilsa, menyuni yana bir joyda qo'shishni eslab qolish kerak.

Nested Routes aynan shu muammo uchun — umumiy qism bir marta yoziladi, faqat o'zgaradigan qism har Route uchun alohida bo'ladi.


Nested Routes nima?

Nested Routes — Route larning ichma-ich joylashtirilishi. Ota Route umumiy layoutni ko'rsatadi. Bola Route lar esa o'sha layout ichidagi o'zgaruvchi qismni ko'rsatadi.

Hayotiy o'xshatma: ofis binosi. Bino bir xil — kirish, liftlar, koridorlar. Lekin har qavat har xil — 1-qavat marketing, 2-qavat texniklar, 3-qavat menejment. Bino qayta qurilmaydi, faqat qavat o'zgaradi.

Nested Routes da bino = ota Route (layout), qavat = bola Route.


Outlet nima?

<Outlet /> — ota Route ning shablonida bola Route ning ko'rsatiladigan joyi. "Bu yerga bola Route ni joylashtir" degan belgi.

import { Outlet } from 'react-router-dom';

function DashboardLayout() {
  return (
    <div>
      <header>Dashboard</header>
      <aside>Yon menyu</aside>
      <main>
        <Outlet /> {/* ← bola Route shu yerda chiqadi */}
      </main>
    </div>
  );
}

Asosiy tuzilish

// App.jsx
import { Routes, Route } from 'react-router-dom';

<Routes>
  <Route path="/dashboard" element={<DashboardLayout />}>
    {/* Bola Route lar — path "/" bilan boshlanmaydi */}
    <Route index element={<DashboardBosh />} />
    <Route path="statistika" element={<Statistika />} />
    <Route path="foydalanuvchilar" element={<Foydalanuvchilar />} />
    <Route path="sozlamalar" element={<Sozlamalar />} />
  </Route>
</Routes>

URL → ko'rsatiladigan komponent:

  • /dashboard → DashboardLayout + DashboardBosh (index)
  • /dashboard/statistika → DashboardLayout + Statistika
  • /dashboard/foydalanuvchilar → DashboardLayout + Foydalanuvchilar
  • /dashboard/sozlamalar → DashboardLayout + Sozlamalar

Har safar DashboardLayout ko'rinadi, faqat <Outlet /> joyi o'zgaradi.


index Route — ota manzilning bosh sahifasi

index prop li Route — ota Route ning URL iga to'g'ri kelganda ko'rsatiladi. /dashboard ga kirganda nima ko'rsatilsin? Aynan index Route.

<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardBosh />} />       {/* /dashboard */}
  <Route path="statistika" element={<Statistika />} /> {/* /dashboard/statistika */}
</Route>

index Route da path yo'q — ota Route ning manzili uning manzili.


To'liq misol — Dashboard

Papkalar tuzilishi:

src/
  layouts/
    DashboardLayout.jsx
  pages/
    dashboard/
      DashboardBosh.jsx
      Statistika.jsx
      Foydalanuvchilar.jsx
      Sozlamalar.jsx
  App.jsx

layouts/DashboardLayout.jsx:

import { Outlet, NavLink } from 'react-router-dom';
import styles from './DashboardLayout.module.css';

const menyuBandlari = [
  { to: '/dashboard', label: 'Bosh', end: true },
  { to: '/dashboard/statistika', label: 'Statistika' },
  { to: '/dashboard/foydalanuvchilar', label: 'Foydalanuvchilar' },
  { to: '/dashboard/sozlamalar', label: 'Sozlamalar' },
];

function DashboardLayout() {
  return (
    <div className={styles.wrapper}>
      <header className={styles.sarlavha}>
        <h2>Boshqaruv paneli</h2>
      </header>

      <div className={styles.kontent}>
        <aside className={styles.menyu}>
          {menyuBandlari.map(({ to, label, end }) => (
            <NavLink
              key={to}
              to={to}
              end={end}
              className={({ isActive }) =>
                isActive ? styles.havola + ' ' + styles.faol : styles.havola
              }
            >
              {label}
            </NavLink>
          ))}
        </aside>

        <main className={styles.asosiy}>
          <Outlet /> {/* ← bola Route shu yerga chiqadi */}
        </main>
      </div>
    </div>
  );
}

export default DashboardLayout;

App.jsx:

import { Routes, Route } from 'react-router-dom';
import DashboardLayout from './layouts/DashboardLayout';
import DashboardBosh from './pages/dashboard/DashboardBosh';
import Statistika from './pages/dashboard/Statistika';
import Foydalanuvchilar from './pages/dashboard/Foydalanuvchilar';
import Sozlamalar from './pages/dashboard/Sozlamalar';
import Bosh from './pages/Bosh';
import TopilmadI from './pages/TopilmadI';

function App() {
  return (
    <Routes>
      <Route path="/" element={<Bosh />} />

      {/* Dashboard — nested routes */}
      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route index element={<DashboardBosh />} />
        <Route path="statistika" element={<Statistika />} />
        <Route path="foydalanuvchilar" element={<Foydalanuvchilar />} />
        <Route path="sozlamalar" element={<Sozlamalar />} />
      </Route>

      <Route path="*" element={<TopilmadI />} />
    </Routes>
  );
}

export default App;

pages/dashboard/DashboardBosh.jsx:

function DashboardBosh() {
  return (
    <div>
      <h1>Xush kelibsiz!</h1>
      <p>Chap menyudan bo'limni tanlang.</p>
    </div>
  );
}

export default DashboardBosh;

pages/dashboard/Statistika.jsx:

function Statistika() {
  return (
    <div>
      <h1>Statistika</h1>
      <p>Bu yerda grafiklar bo'ladi.</p>
    </div>
  );
}

export default Statistika;

Ko'p darajali nested routes

Nested routes faqat ikki darajali bo'lishi shart emas. Uch, to'rt daraja ham bo'lishi mumkin:

<Routes>
  <Route path="/ilova" element={<IlovaLayout />}>
    <Route path="mahsulotlar" element={<MahsulotlarLayout />}>
      <Route index element={<MahsulotlarRoyxat />} />
      <Route path=":id" element={<MahsulotDetali />} />
      <Route path=":id/tahrir" element={<MahsulotTahrir />} />
    </Route>
    <Route path="buyurtmalar" element={<Buyurtmalar />} />
  </Route>
</Routes>

URL → natija:

  • /ilova/mahsulotlar → IlovaLayout + MahsulotlarLayout + MahsulotlarRoyxat
  • /ilova/mahsulotlar/42 → IlovaLayout + MahsulotlarLayout + MahsulotDetali
  • /ilova/mahsulotlar/42/tahrir → IlovaLayout + MahsulotlarLayout + MahsulotTahrir
  • /ilova/buyurtmalar → IlovaLayout + Buyurtmalar

Har daraja o'zining <Outlet /> ini ko'rsatadi.


Outlet ga ma'lumot uzatish

Ota Route bola Route ga useOutletContext orqali ma'lumot uzata oladi:

import { Outlet } from 'react-router-dom';

function DashboardLayout() {
  const foydalanuvchi = { ism: 'Jasur', rol: 'admin' };

  return (
    <div>
      <Outlet context={foydalanuvchi} /> {/* ← context uzatilmoqda */}
    </div>
  );
}
import { useOutletContext } from 'react-router-dom';

function Statistika() {
  const foydalanuvchi = useOutletContext(); // ← context olinmoqda

  return <h1>Salom, {foydalanuvchi.ism}!</h1>;
}

Lekin bu ko'p ishlatilmaydi — odatda Context API yoki global state bilan hal qilinadi. Faqat oddiy holatlarda qulay.


Tez-tez uchraydigan xatolar

1. Ota layout da <Outlet /> ni unutish

// ❌ Xato — Outlet yo'q
function DashboardLayout() {
  return (
    <div>
      <aside>Menyu</aside>
      <main>
        {/* Outlet yo'q — bola Route lar hech qachon ko'rinmaydi */}
      </main>
    </div>
  );
}
// ✅ To'g'ri
function DashboardLayout() {
  return (
    <div>
      <aside>Menyu</aside>
      <main>
        <Outlet /> {/* ← shu yerga bola Route chiqadi */}
      </main>
    </div>
  );
}

Sababi: <Outlet /> bo'lmasa React Router bola Route ni qayerga joylashtirish kerakligini bilmaydi. Bola Route lar tanlanadi, lekin ekranda hech narsa ko'rinmaydi.


2. Bola Route path da / bilan boshlash

// ❌ Xato — bola route path "/" bilan boshlanmoqda
<Route path="/dashboard" element={<DashboardLayout />}>
  <Route path="/dashboard/statistika" element={<Statistika />} />
</Route>

// ✅ To'g'ri — faqat qo'shimcha qism
<Route path="/dashboard" element={<DashboardLayout />}>
  <Route path="statistika" element={<Statistika />} />
</Route>

Sababi: Bola Route path ota Route path iga avtomatik qo'shiladi. /dashboard + statistika = /dashboard/statistika. Agar / bilan boshlasangiz — mutlaq yo'l bo'lib, ota-bola munosabati buziladi va Route to'g'ri ishlamaydi.


3. index o'rniga path="/" yozish

// ❌ Xato — nested route da path="/"
<Route path="/dashboard" element={<DashboardLayout />}>
  <Route path="/" element={<DashboardBosh />} />
</Route>

// ✅ To'g'ri — index prop
<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardBosh />} />
</Route>

Sababi: path="/" nested Route da noto'g'ri — u mutlaq manzilga ishora qiladi. index prop esa "ota Route ning URL i bilan bir xil bo'lganda ko'rsat" degan ma'no beradi.


4. NavLink da noto'g'ri to manzil

// ❌ Xato — to da ota path yo'q
<NavLink to="statistika">Statistika</NavLink>
// Natija: joriy URL ga "statistika" qo'shiladi
// /dashboard da ishlaydi, lekin /dashboard/boshqa yerda buzilib ketadi

// ✅ To'g'ri — to'liq manzil
<NavLink to="/dashboard/statistika">Statistika</NavLink>

Sababi: Nisbiy to joriy URL ga qarab ishlaydi — turgan joyingizga qarab har xil natija beradi. Menyu havolalarida har doim mutlaq manzil (/ bilan boshlanadigan) ishlating.


Xulosa

  • Nested Routes — umumiy layout bir marta, faqat o'zgaruvchi qism alohida
  • <Outlet /> — ota layout da bola Route ni chiqarish joyi
  • index — ota Route URL iga to'g'ri kelganda ko'rsatiladigan bola Route
  • Bola Route path da / boshlanmaydi — faqat qo'shimcha qism yoziladi
  • <Outlet context={...}> va useOutletContext() — ota-bola ma'lumot uzatish
  • Menyu havolalarida to'liq manzil (/dashboard/statistika) ishlatiladi

Keyingi dars: 46-dars: Protected Route — faqat login qilganlar uchun