nmk

Практична робота №7 (2 години)

Тема: Робота з URL-параметрами. Створення динамічних посилань та обробка фільтрів пошуку через Query String за допомогою React Router.

Мета: Навчитися керувати станом додатку не лише всередині пам’яті компонентів (через useState), а й синхронізувати його з адресним рядком браузера (URL); опанувати хуки useParams для реалізації сторінок деталей об’єктів та useSearchParams для реалізації систем пошуку, сортування та фільтрації, що дозволить користувачам ділитися посиланнями на конкретні результати.

Необхідні інструменти: Браузер, Node.js, редактор VS Code, локально запущений проєкт з попередніх етапів з встановленою бібліотекою react-router-dom.

План заняття

  1. Аналіз маршрутизації додатку та створення динамічного маршруту.
  2. Використання хука useParams для отримання ID елемента з URL.
  3. Додавання рядка пошуку (Search Bar) до списку елементів.
  4. Синхронізація введеного тексту з URL за допомогою useSearchParams.
  5. Фільтрація загального масиву даних на основі параметрів з адресного рядка.

Хід виконання роботи

1. Аналіз та налаштування динамічного маршруту

У вашому єдиному репозиторії вже налаштований react-router-dom (з Лабораторної №4). Наразі у вас є Головна сторінка зі стрічкою новин (або списком користувачів). Тепер ми хочемо, щоб при кліку на конкретний пост, відкривалася окрема сторінка з його деталями.

Відкрийте файл, де прописані ваші Routes (зазвичай це App.jsx або App.tsx), і додайте новий динамічний маршрут (Route). Двокрапка : вказує на динамічний параметр:

// src/App.jsx
import { Routes, Route } from "react-router-dom";
import NewsFeed from "./pages/NewsFeed";
import PostDetails from "./pages/PostDetails";

function App() {
  return (
    <Routes>
      <Route path="/" element={<NewsFeed />} />
      {/* Динамічний маршрут: postId буде змінною */}
      <Route path="/post/:postId" element={<PostDetails />} />
    </Routes>
  );
}

2. Читання параметрів за допомогою useParams

Коли користувач переходить за посиланням /post/42, компонент PostDetails повинен зрозуміти, що йому треба у API запросити пост саме з ID 42. Для цього існує хук useParams.

Створіть (або відредагуйте) компонент PostDetails:

// src/pages/PostDetails.jsx
import { useParams, Link } from "react-router-dom";
import { useEffect, useState } from "react";

const PostDetails = () => {
  // Витягуємо параметр з URL. Назва postId має збігатися з назвою в <Route path="/post/:postId">
  const { postId } = useParams();
  const [post, setPost] = useState(null);

  useEffect(() => {
    // В Лабі 6 ми створювали useFetch, тут просто наочний приклад:
    fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
      .then((res) => res.json())
      .then((data) => setPost(data));
  }, [postId]); // Обов'язково додаємо параметр в залежності

  if (!post) return <p>Завантаження поста #{postId}...</p>;

  return (
    <div>
      <Link to="/">&larr; Назад до списку</Link>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
};

export default PostDetails;

Тепер у списку постів (у NewsFeed.jsx) обгорніть кожен PostCard у компонент <Link to={\/post/${post.id}`}>`.

Тепер зробимо так, щоб користувач міг шукати пости за назвою, і цей пошук зберігався в URL у вигляді /?query=react. Відкрийте сторінку NewsFeed і додайте базову форму пошуку.

// src/pages/NewsFeed.jsx (Фрагмент коду)
import { useSearchParams } from "react-router-dom";

const NewsFeed = () => {
  // Хук працює схоже на useState, але зберігає дані прямо в адресному рядку браузера
  const [searchParams, setSearchParams] = useSearchParams();

  // Дістаємо поточне значення query. Якщо його немає в URL, буде пустий рядок
  const searchQuery = searchParams.get("query") || "";

  const handleSearchChange = (e) => {
    const text = e.target.value;
    if (text) {
      // Записуємо в URL (напр. ?query=тест)
      setSearchParams({ query: text });
    } else {
      // Якщо інпут порожній - видаляємо параметр з URL
      setSearchParams({});
    }
  };

  return (
    <div>
      <input
        type="text"
        placeholder="Пошук новин..."
        value={searchQuery} // Контрольований компонент, що залежить від URL
        onChange={handleSearchChange}
      />
      {/* Тут рендер списку */}
    </div>
  );
};

4. Фільтрація даних на основі URL

Тепер, коли пошуковий запит лежить у змінній searchQuery (і живиться безпосередньо від URL), ми можемо відфільтрувати масив даних перед тим, як рендерити його.

// Продовження NewsFeed.jsx

// Припускаємо, що postsData - це масив постів з API (з вашого useFetch)
const postsData = [
  { id: 1, title: "Вивчення React Router" },
  { id: 2, title: "Новини JavaScript" },
  { id: 3, title: "Що нового у Vite" },
];

// Динамічна фільтрація: працює щоразу при зміні URL
const filteredPosts = postsData.filter((post) =>
  post.title.toLowerCase().includes(searchQuery.toLowerCase()),
);

return (
  <div>
    {/* ... Поле інпуту з Кроку 3 ... */}

    <div className="posts-list">
      {filteredPosts.length > 0 ? (
        filteredPosts.map((post) => <p key={post.id}>{post.title}</p>)
      ) : (
        <p>За вашим запитом "{searchQuery}" нічого не знайдено.</p>
      )}
    </div>
  </div>
);

Перевірте роботу в браузері: введіть текст у пошук. Зверніть увагу, як змінюється URL-адреса. Спробуйте оновити сторінку (F5) — пошук не скинеться, оскільки стан “виживає” завдяки тому, що він зберігається в URL!

Завдання для самостійного виконання

  1. В єдиному репозиторії, до вашої Стрічки новин (або списку користувачів) додайте нову фічу: Фільтрацію за категоріями або Сортування.
  2. Створіть випадаючий список (<select>), який дозволятиме сортувати елементи “Від А до Я” або “Від Я до А” (або за датою).
  3. Зробіть так, щоб обране сортування зберігалося в URL (наприклад: /?query=react&sort=desc). Підказка: вам доведеться передавати існуючі параметри разом з новими при виклику setSearchParams.
  4. Напишіть логіку, яка застосовує спочатку фільтрацію (пошук), а потім до відфільтрованого масиву застосовує метод .sort().
  5. Розкажіть викладачу, чому можливість надіслати посилання з параметрами (?query=abc) іншому користувачеві в месенджері є важливою бізнес-вимогою сучасних веб-додатків.

Контрольні запитання

  1. Поясніть принципову різницю між URL-параметрами шляху (Path Parameters, наприклад /users/42) та параметрами запиту (Query Parameters, наприклад /users?sort=asc). В яких випадках який підхід слід використовувати?
  2. Що робить хук useParams() і який обов’язковий синтаксис необхідно використати в визначенні <Route path="..."> для того, щоб цей хук запрацював?
  3. Чому зберігання стану пошукового рядка (Search Bar) в URL (через useSearchParams) вважається архітектурно кращим рішенням, ніж зберігання його у локальному стані компонента (useState)? Опишіть переваги для кінцевого користувача.
  4. Якщо ви використовуєте метод масиву filter() прямо перед поверненням JSX-розмітки, чи відбувається ця фільтрація при кожному рендері компонента? Як це може вплинути на продуктивність для дуже великих масивів (напр. 10 000+ елементів) і який інший React-хук ви б використали для оптимізації?
  5. В яких об’єктах стандарту Web API у звичайному (базовому) JavaScript зберігаються параметри після знаку питання ?, і як React Router спрощує роботу з ними?