Пошук уроків, статей та іншого контенту
Працюватимете з параметрами пошукового рядка через useSearchParams для фільтрації, сортування та пагінації.
Пошуковий рядок URL — частина адреси після символу ?:
/products?category=books&sort=price-asc&page=2Параметри пошукового рядка зручні для стану, який має:
зберігатися після перезавантаження сторінки;
бути доступним за прямим посиланням;
працювати з кнопками «Назад» і «Вперед» у браузері;
передаватися іншим користувачам через URL.
У React Router для роботи з такими параметрами використовується хук useSearchParams.
useSearchParamsІмпортуйте хук із react-router-dom:
import { useSearchParams } from "react-router-dom";Хук повертає пару значень:
const [searchParams, setSearchParams] = useSearchParams();searchParams — об’єкт URLSearchParams для читання параметрів;
setSearchParams — функція для зміни параметрів і URL.
Наприклад, для URL:
/products?category=books&page=2можна отримати значення так:
const category = searchParams.get("category");
const page = searchParams.get("page");
console.log(category); // "books"
console.log(page); // "2"Усі значення з URL є рядками. Якщо параметр використовується як число, його потрібно перетворити:
const page = Number(searchParams.get("page")) || 1;Метод get повертає null, якщо параметра немає.
Змінити параметри можна за допомогою setSearchParams:
setSearchParams({
category: "books",
page: "1",
});Після цього URL стане таким:
/products?category=books&page=1Значення краще передавати як рядки, оскільки пошуковий рядок URL зберігає саме текстові значення.
Щоб видалити параметр, передайте порожній рядок або створіть новий набір параметрів без цього ключа:
setSearchParams({
category: "",
page: "1",
});У результаті category може бути представлений як category=. Для повного видалення параметра зручніше використати функцію оновлення:
setSearchParams((currentParams) => {
const nextParams = new URLSearchParams(currentParams);
nextParams.delete("category");
nextParams.set("page", "1");
return nextParams;
});Розглянемо компонент каталогу. Категорія товарів зберігається в параметрі category:
/products?category=booksЗначення параметра можна використати під час фільтрації масиву:
const category = searchParams.get("category") || "";
const filteredProducts = products.filter((product) => {
return category === "" || product.category === category;
});Якщо category відсутній або порожній, показуються всі товари.
Обробник зміни вибраної категорії:
function handleCategoryChange(event) {
const category = event.target.value;
setSearchParams((currentParams) => {
const nextParams = new URLSearchParams(currentParams);
if (category) {
nextParams.set("category", category);
} else {
nextParams.delete("category");
}
nextParams.set("page", "1");
return nextParams;
});
}Після зміни фільтра сторінку пагінації потрібно скинути на першу. Інакше користувач може залишитися, наприклад, на сторінці 5, де після фільтрації вже немає товарів.
Параметр sort може містити напрям і поле сортування:
/products?sort=price-asc
/products?sort=price-descЗначення параметра читається так:
const sort = searchParams.get("sort") || "name-asc";Далі масив можна відсортувати:
const sortedProducts = [...filteredProducts].sort((firstProduct, secondProduct) => {
if (sort === "price-asc") {
return firstProduct.price - secondProduct.price;
}
if (sort === "price-desc") {
return secondProduct.price - firstProduct.price;
}
return firstProduct.name.localeCompare(secondProduct.name);
});Створення копії через [...filteredProducts] важливе: метод sort змінює масив, на якому викликається.
Номер сторінки читаємо з URL:
const page = Number(searchParams.get("page")) || 1;
const itemsPerPage = 3;Для отримання частини масиву використовуємо slice:
const startIndex = (page - 1) * itemsPerPage;
const visibleProducts = sortedProducts.slice(
startIndex,
startIndex + itemsPerPage
);Кількість сторінок:
const totalPages = Math.ceil(sortedProducts.length / itemsPerPage);Під час переходу на іншу сторінку змінюємо лише параметр page, зберігаючи інші параметри:
function goToPage(nextPage) {
setSearchParams((currentParams) => {
const nextParams = new URLSearchParams(currentParams);
nextParams.set("page", String(nextPage));
return nextParams;
});
}У прикладі нижче:
category відповідає за фільтрацію;
sort відповідає за сортування;
page відповідає за пагінацію;
усі значення зберігаються в URL.
import { BrowserRouter, useSearchParams } from "react-router-dom";
const products = [
{ id: 1, name: "React для початківців", category: "books", price: 450 },
{ id: 2, name: "JavaScript довідник", category: "books", price: 380 },
{ id: 3, name: "Бездротова клавіатура", category: "hardware", price: 1200 },
{ id: 4, name: "USB-мікрофон", category: "hardware", price: 2100 },
{ id: 5, name: "Навушники", category: "hardware", price: 850 },
{ id: 6, name: "CSS практикум", category: "books", price: 520 },
{ id: 7, name: "Підставка для ноутбука", category: "hardware", price: 900 },
];
function ProductCatalog() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get("category") || "";
const sort = searchParams.get("sort") || "name-asc";
const page = Math.max(1, Number(searchParams.get("page")) || 1);
const itemsPerPage = 3;
const filteredProducts = products.filter((product) => {
return category === "" || product.category === category;
});
const sortedProducts = [...filteredProducts].sort(
(firstProduct, secondProduct) => {
if (sort === "price-asc") {
return firstProduct.price - secondProduct.price;
}
if (sort === "price-desc") {
return secondProduct.price - firstProduct.price;
}
return firstProduct.name.localeCompare(secondProduct.name);
}
);
const totalPages = Math.max(
1,
Math.ceil(sortedProducts.length / itemsPerPage)
);
const safePage = Math.min(page, totalPages);
const startIndex = (safePage - 1) * itemsPerPage;
const visibleProducts = sortedProducts.slice(
startIndex,
startIndex + itemsPerPage
);
function updateParams(changes) {
setSearchParams((currentParams) => {
const nextParams = new URLSearchParams(currentParams);
Object.entries(changes).forEach(([key, value]) => {
if (value === "" || value === null) {
nextParams.delete(key);
} else {
nextParams.set(key, String(value));
}
});
return nextParams;
});
}
function handleCategoryChange(event) {
updateParams({
category: event.target.value,
page: 1,
});
}
function handleSortChange(event) {
updateParams({
sort: event.target.value,
page: 1,
});
}
function goToPage(nextPage) {
updateParams({ page: nextPage });
}
return (
<main>
<h1>Каталог товарів</h1>
<label>
Категорія{" "}
<select value={category} onChange={handleCategoryChange}>
<option value="">Усі категорії</option>
<option value="books">Книги</option>
<option value="hardware">Техніка</option>
</select>
</label>
{" "}
<label>
Сортування{" "}
<select value={sort} onChange={handleSortChange}>
<option value="name-asc">За назвою</option>
<option value="price-asc">Від дешевих до дорогих</option>
<option value="price-desc">Від дорогих до дешевих</option>
</select>
</label>
<ul>
{visibleProducts.map((product) => (
<li key={product.id}>
{product.name} — {product.price} грн
</li>
))}
</ul>
{visibleProducts.length === 0 && <p>Товарів не знайдено.</p>}
<div>
<button
type="button"
disabled={safePage === 1}
onClick={() => goToPage(safePage - 1)}
>
Попередня
</button>
<span>
{" "}
Сторінка {safePage} з {totalPages}{" "}
</span>
<button
type="button"
disabled={safePage === totalPages}
onClick={() => goToPage(safePage + 1)}
>
Наступна
</button>
</div>
</main>
);
}
export default function App() {
return (
<BrowserRouter>
<ProductCatalog />
</BrowserRouter>
);
}Для запуску цього прикладу застосунок має використовувати react-router-dom, а компонент повинен бути дочірнім елементом BrowserRouter. Хук useSearchParams працює в контексті маршрутизатора.
Для одного параметра достатньо get:
const query = searchParams.get("query") || "";Якщо один ключ може повторюватися, використовуйте getAll:
/products?tag=books&tag=newconst tags = searchParams.getAll("tag");
console.log(tags); // ["books", "new"]Для звичайних фільтрів, сортування та пагінації найчастіше достатньо одного значення на кожен параметр.
Поширена помилка — повністю замінити пошуковий рядок під час зміни одного параметра:
setSearchParams({ page: "2" });Такий виклик видалить category і sort, якщо вони були в URL.
Щоб зберегти наявні параметри, створіть копію поточного набору:
setSearchParams((currentParams) => {
const nextParams = new URLSearchParams(currentParams);
nextParams.set("page", "2");
return nextParams;
});Тепер URL на кшталт:
/products?category=books&sort=price-asc&page=1перетвориться на:
/products?category=books&sort=price-asc&page=2Параметри URL завжди є рядками:
const page = searchParams.get("page");
console.log(page + 1); // "21", якщо page дорівнює "2"Перетворюйте значення перед математичними операціями:
const page = Number(searchParams.get("page")) || 1;
console.log(page + 1); // 3Параметр може бути відсутнім під час першого відкриття сторінки:
const category = searchParams.get("category");У такому разі category дорівнює null. Зазвичай зручно одразу задати значення за замовчуванням:
const category = searchParams.get("category") || "";Якщо користувач перебуває на останній сторінці, а новий фільтр повертає лише один результат, поточна сторінка може виявитися порожньою.
Під час зміни фільтра або сортування встановлюйте:
nextParams.set("page", "1");searchParams безпосередньоНе змінюйте поточний об’єкт без створення копії:
searchParams.set("page", "2");Для передбачуваного оновлення використовуйте новий об’єкт:
setSearchParams((currentParams) => {
const nextParams = new URLSearchParams(currentParams);
nextParams.set("page", "2");
return nextParams;
});setSearchParams самостійно кодує пробіли та спеціальні символи:
setSearchParams({
query: "react router",
});URL буде сформовано з коректним кодуванням, а отримати початковий текст можна через:
const query = searchParams.get("query") || "";useSearchParams дає змогу читати та змінювати пошуковий рядок URL.
searchParams.get("name") отримує значення параметра.
Значення з URL завжди мають тип string.
setSearchParams змінює URL і спричиняє повторний рендер компонента.
Для фільтрації параметри можна застосовувати безпосередньо до масиву даних.
Під час сортування не змінюйте початковий масив — спочатку створіть його копію.
Під час пагінації перетворюйте номер сторінки на число.
Після зміни фільтра або сортування зазвичай потрібно повертати page до 1.
Щоб не втрачати інші параметри, оновлюйте копію поточного URLSearchParams.