Тема: Інтеграція системи фільтрації та пошуку. Робота з локальним станом для забезпечення миттєвого відгуку інтерфейсу на дії користувача.
Мета: Навчитися використовувати хук useState для керування станом додатку; реалізувати функціонал фільтрації масивів даних у реальному часі; опанувати концепцію “керованих компонентів” (controlled components) при роботі з формами.
Технологічний стек: React, Vite, CSS Modules, Hooks (``).
useState для збереження поточного запиту пошуку та обраної категорії.Створимо кероване поле вводу, яке буде передавати дані у батьківський компонент.
src/components/molecules/SearchBar/SearchBar.jsx та SearchBar.module.css.value та функцію onChange через props.SearchBar.jsx:
import styles from "./SearchBar.module.css";
const SearchBar = ({ searchTerm, onSearchChange }) => {
return (
<div className={styles.searchWrapper}>
<input
type="text"
placeholder="Пошук постів..."
value={searchTerm}
onChange={(e) => onSearchChange(e.target.value)}
className={styles.searchInput}
/>
</div>
);
};
export default SearchBar;
Додамо можливість фільтрувати пости за категоріями (наприклад: “Усі”, “Новини”, “Робота”).
category до кожного об’єкта.App.jsx створіть стан для активної категорії:const [activeCategory, setActiveCategory] = useState("All");
Тепер необхідно об’єднати пошуковий запит та фільтр категорій перед рендерингом списку.
App.jsx:
import { useState } from "react";
import { postsData } from "./data";
import Post from "./components/molecules/Post/Post";
import SearchBar from "./components/molecules/SearchBar/SearchBar";
import styles from "./App.module.css";
function App() {
const [searchTerm, setSearchTerm] = useState("");
const [activeCategory, setActiveCategory] = useState("All");
// Логіка фільтрації
const filteredPosts = postsData.filter((post) => {
const matchesSearch =
post.content.toLowerCase().includes(searchTerm.toLowerCase()) ||
post.author.toLowerCase().includes(searchTerm.toLowerCase());
const matchesCategory =
activeCategory === "All" || post.category === activeCategory;
return matchesSearch && matchesCategory;
});
return (
<div className={styles.appContainer}>
<h1>Стрічка з фільтрацією</h1>
<SearchBar searchTerm={searchTerm} onSearchChange={setSearchTerm} />
<div className={styles.filters}>
{["All", "News", "Updates"].map((cat) => (
<button
key={cat}
onClick={() => setActiveCategory(cat)}
className={activeCategory === cat ? styles.active : ""}
>
{cat}
</button>
))}
</div>
<div className={styles.feed}>
{filteredPosts.length > 0 ? (
filteredPosts.map((post) => <Post key={post.id} {...post} />)
) : (
<p className={styles.empty}>Нічого не знайдено за вашим запитом.</p>
)}
</div>
</div>
);
}
export default App;
SearchBar?useState.filteredPosts, а не змінюємо оригінальний масив postsData у стані?lab_03.md додати: