Індексні маршрути

На більшості інтернет-сайтів певна частина інтерфейсу лишається незмінно. Наприклад шапка, підвал, бокове меню тощо. А при навігації по сайту змінюється лише вміст головного контейнера.

Немає сенсу на кожній сторінці дублювати цю розмітку. Можна зробити спільну обгортку із компонентів, які не змінюються, а при навігації по сайту змінювати тільки головний вміст. Для цього використовують прийом «shared layout».

src/components/App.jsx
// Import libraries and components

export const App = () => {
  return (
    <Container>
      <Header>
        <Logo>
          <span role="img" aria-label="logo icon">
            🙈
          </span>
          Monkey Studio
        </Logo>
        <nav>
           <Link to="/">Home</Link>
           <Link to="/about">About</Link>
           <Link to="/reviews">Reviews</Link>
           <Link to="/works">Works</Link>
        </nav>
      </Header>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />}>
           <Route path="team" element={<Team />} />
           <Route path="mission" element={<Mission />} />
           <Route path="founders" element={<Founders />} />
           <Route path="partners" element={<Partners />} />
        </Route>
        <Route path="/reviews" element={<Reviews />} />
        <Route path="/works" element={<Works />} />
        <Route path="/works/:workId" element={<WorkDetails />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </Container>
  );
};

У прикладі вище, головне меню навігації зверстано у файлі App.jsx. При нарощенні проєкту, стилізаційні елементи сильно перенавантажуватимуть App.jsx. Прийнято у файлі App.jsx прописувати тільки правила маршрутизації, без зайвого контенту, а всі стилізаційні компоненти та верстання незмінного блоку виносити в окремі блоки.

Тож, винесемо розмітку і стилі незмінної обгортки в окремий компонент <SharedLayout>. Там де ми плануємо рендерити змінну частину сайту в залежності маршруту на якому перебуваємо - вказуємо компонент <Outlet>.

src/components/SharedLayout.jsx
// Import libraries and components
import { Outlet } from "react-router-dom";

export const SharedLayout = () => {
  return (
    <Container>
      <Header>
        <Logo>
          <span role="img" aria-label="logo icon">
            🙈
          </span>
          Monkey Studio
        </Logo>
        <nav>
           <Link to="/">Home</Link>
           <Link to="/about">About</Link>
           <Link to="/reviews">Reviews</Link>
           <Link to="/works">Works</Link>
        </nav>
      </Header>
      <Outlet />
    </Container>
  );
};

Після цього потрібно використати компонент <SharedLayout> в компоненті <App>. Пропишемо так, щоб він рендерився на всіх маршрутах вкажемо його маршрут path="/". Вкладемо у нього всі інші маршрути АЛЕ маршрути пропишемо відносно батьківського Shared Layout - тобто prop path без символу /.

src/components/App.jsx
// Import libraries and components
import { SharedLayout } from "path/to/components/SharedLayout";

export const App = () => {
  return (
    <Routes>
      <Route path="/" element={<SharedLayout />}>
        <Route path="about" element={<About />}>
           <Route path="team" element={<Team />} />
           <Route path="mission" element={<Mission />} />
           <Route path="founders" element={<Founders />} />
           <Route path="partners" element={<Partners />} />
        </Route>
        <Route path="reviews" element={<Reviews />} />
        <Route path="works" element={<Works />} />
        <Route path="works/:workId" element={<WorkDetails />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  );
};

Тут видно, що за базовим маршрутом path="/" тепер рендериться тільки компонент <SharedLayout> без <Home>. Хоча за іншими вкладеними маршрутами рендериться як <SharedLayout>, так і той компонент у якого збігається path зі значенням в адресному рядку. Тобто за маршрутом path="about" зрендериться компонент <SharedLayout> і <About>.

Щоб виправити цей ньюанс і за базовим маршрутом рендерити <Home> компонент за тим же маршрутом необхідно створити індексний маршрут.

src/components/App.jsx
// Import libraries and components
import { SharedLayout } from "path/to/components/SharedLayout";

export const App = () => {
  return (
    <Routes>
      <Route path="/" element={<SharedLayout />}>
        <Route index element={<Home />} />
        <Route path="about" element={<About />}>
           <Route path="team" element={<Team />} />
           <Route path="mission" element={<Mission />} />
           <Route path="founders" element={<Founders />} />
           <Route path="partners" element={<Partners />} />
        </Route>
        <Route path="reviews" element={<Reviews />} />
        <Route path="works" element={<Works />} />
        <Route path="works/:workId" element={<WorkDetails />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Routes>
  );
};

Індексним може бути лише вкладений маршрут. В його <Route> не вказують prop path, тому що він збігається зі значенням path батька. Замість цього передають спеціальний prop index, який вказує бібліотеці React Router, що цей маршрут треба відрендерити на ту ж адресу, що і його батько.

Індексних маршрутів може бути довільна кількість залежно від поставленого завдання. Наприклад, у панелі адміністратора можна рендерити інший інтерфейс і відповідно підключити інший <SharedLayout>.

src/components/App.jsx
// Import libraries and components
import { SharedLayout } from "path/to/components/SharedLayout";

export const App = () => {
  return (
    <Routes>
      <Route path="/" element={<SharedLayout />}>
        <Route index element={<Home />} />
        <Route path="about" element={<About />}>
           <Route path="team" element={<Team />} />
           <Route path="mission" element={<Mission />} />
           <Route path="founders" element={<Founders />} />
           <Route path="partners" element={<Partners />} />
        </Route>
        <Route path="reviews" element={<Reviews />} />
        <Route path="works" element={<Works />} />
        <Route path="works/:workId" element={<WorkDetails />} />
        <Route path="*" element={<NotFound />} />
      </Route>
      <Route path="/admin" element={<AdminLayout />}>
         <Route index element={<Dashboard />} />
         <Route path="sales" element={<Sales />} />
         <Route path="customers" element={<Customers />} />
      </Route>
    </Routes>
  );
};

Нижче наведено живий приклад на маршрутизацію із SharedLayout. Проглянути код можна тут.

Last updated