Тема: Робота з URL-параметрами. Створення динамічних посилань та обробка фільтрів пошуку через Query String за допомогою React Router.
Мета: Навчитися керувати станом додатку не лише всередині пам’яті компонентів (через useState), а й синхронізувати його з адресним рядком браузера (URL); опанувати хуки useParams для реалізації сторінок деталей об’єктів та useSearchParams для реалізації систем пошуку, сортування та фільтрації, що дозволить користувачам ділитися посиланнями на конкретні результати.
Необхідні інструменти: Браузер, Node.js, редактор VS Code, локально запущений проєкт з попередніх етапів з встановленою бібліотекою react-router-dom.
useParams для отримання ID елемента з URL.useSearchParams.У вашому єдиному репозиторії вже налаштований 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>
);
}
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="/">← Назад до списку</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>
);
};
Тепер, коли пошуковий запит лежить у змінній 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!
<select>), який дозволятиме сортувати елементи “Від А до Я” або “Від Я до А” (або за датою)./?query=react&sort=desc). Підказка: вам доведеться передавати існуючі параметри разом з новими при виклику setSearchParams..sort().?query=abc) іншому користувачеві в месенджері є важливою бізнес-вимогою сучасних веб-додатків./users/42) та параметрами запиту (Query Parameters, наприклад /users?sort=asc). В яких випадках який підхід слід використовувати?useParams() і який обов’язковий синтаксис необхідно використати в визначенні <Route path="..."> для того, щоб цей хук запрацював?useSearchParams) вважається архітектурно кращим рішенням, ніж зберігання його у локальному стані компонента (useState)? Опишіть переваги для кінцевого користувача.filter() прямо перед поверненням JSX-розмітки, чи відбувається ця фільтрація при кожному рендері компонента? Як це може вплинути на продуктивність для дуже великих масивів (напр. 10 000+ елементів) і який інший React-хук ви б використали для оптимізації??, і як React Router спрощує роботу з ними?