React хранит строку поиска в состоянии, а отфильтрованные карточки вычисляет заново при каждом вводе. Соберём каталог из локального массива, чтобы не зависеть от ключа API. Вы увидите компонент, props, useState, map, filter и сообщение для запроса, по которому ничего не найдено.
В обычном JavaScript легко смешать данные, поиск и ручное изменение DOM. React позволяет описать, какой интерфейс соответствует текущему query. В этой статье вы соберёте адаптивный каталог с шестью фильмами и живым поиском. Финальный результат можно проверить без догадок: запрос space оставляет две карточки, неизвестное слово показывает пустое состояние, а очистка возвращает весь список.
Массив movies остаётся обычным JavaScript и не меняется во время поиска.
useState хранит только то, что ввёл пользователь.
filter и map превращают query в набор карточек без ручного innerHTML.
Как устроен проект React на Vite
Vite отвечает за dev-сервер и сборку, main.jsx монтирует корневой компонент, App.jsx хранит экран каталога. Компонент MovieCard получает один объект через props и не знает о строке поиска. App вычисляет visibleMovies из исходного массива. Для React и Vite держите этапы раздельно: ввод, проверка, основное действие и вывод. Такой маршрут позволяет остановиться в конкретной точке, увидеть фактические данные и исправить одну причину вместо случайной перестановки строк.
| Этап | Действие | Проверяемый результат |
|---|---|---|
| Ввод | onChange получает value из input | query меняется |
| Рендер | React повторно вызывает App | Получено новое дерево интерфейса |
| Фильтр | title и genre сравниваются с query | Собран visibleMovies |
| Карточки | map создаёт MovieCard с key | На экране только совпадения |
Источник истины один: строка query. Список не копируется в отдельное состояние и не рассинхронизируется. У React и Vite каждый этап оставляет наблюдаемый след, поэтому следующий шаг начинается только после проверки предыдущего.

От входных данных до видимого результата. Query является единственным изменяемым значением
Пишем каталог и живой поиск
Установите актуальный Node.js, затем выполните npm create vite@latest movie-search -- --template react, cd movie-search, npm install и npm run dev. Актуальная документация Vite требует поддерживаемую версию Node.js и печатает предупреждение, если среда устарела. Замените src/App.jsx и src/index.css файлами из проекта статьи.
import { useState } from "react";
const movies = [
{ id: 1, title: "Deep Space", genre: "Sci-Fi", year: 2026 },
{ id: 2, title: "Quiet Code", genre: "Drama", year: 2024 },
{ id: 3, title: "Space Garden", genre: "Adventure", year: 2025 },
{ id: 4, title: "Pixel City", genre: "Animation", year: 2023 },
];
function MovieCard({ movie }) {
return (
<article className="card">
<span>{movie.genre}</span>
<h2>{movie.title}</h2>
<p>{movie.year}</p>
</article>
);
}
export default function App() {
const [query, setQuery] = useState("");
const normalized = query.trim().toLowerCase();
const visibleMovies = movies.filter((movie) =>
[movie.title, movie.genre].join(" ").toLowerCase().includes(normalized)
);
return (
<main>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Название или жанр"
/>
<p>Найдено: {visibleMovies.length}</p>
<section className="grid">
{visibleMovies.map((movie) => (
<MovieCard key={movie.id} movie={movie} />
))}
</section>
{visibleMovies.length === 0 && <p>Ничего не найдено</p>}
</main>
);
}- При пустом query видны все карточки.
- Поиск не зависит от регистра и пробелов по краям.
- Счётчик совпадает с количеством карточек.
- Нулевой список показывает отдельное сообщение.
Компонент хранит только query. visibleMovies является результатом вычисления и создаётся заново из неизменяемого массива. Пример React и Vite специально ограничен одним пользовательским сценарием, чтобы назначение каждой строки оставалось видимым.

Что отличает устойчивый проект от случайного запуска. React строит представление из данных
Почему derived data не нужно хранить в state
useState возвращает текущее значение и функцию обновления. setQuery планирует новый рендер, после чего App выполняется снова. React сравнивает новое дерево с предыдущим и меняет только нужные DOM-узлы. Атрибут key связывает карточку с movie.id, поэтому React понимает идентичность элементов. Если использовать индекс массива, сортировка может перенести локальное состояние не к той карточке. Контролируемый input получает value из state, поэтому поле и query всегда совпадают.
| Часть | За что отвечает | Что проверить |
|---|---|---|
| Vite | Запускает dev-сервер и build | npm run dev открывает адрес |
| App | Хранит query и фильтрует данные | Нет копии visibleMovies в state |
| MovieCard | Рисует один фильм | Получает props movie |
| useState | Хранит пользовательский ввод | Обновляется через setQuery |
| key | Идентифицирует карточку | Используется стабильный id |
Фильтр выполняется при каждом рендере, и для шести карточек это дешевле лишнего состояния. useMemo нужен только после реального замера на большом массиве или дорогом вычислении. trim убирает пробелы по краям, toLowerCase делает сравнение нечувствительным к регистру. Для русских названий этого обычно достаточно, а сложный поиск по опечаткам является отдельной задачей. Не меняйте movies через sort на месте: методы, изменяющие массив, могут испортить исходные данные. Создавайте копию через [...movies].sort(...).
Проверяем поиск, регистр и пустой результат
Запустите каталог и проверьте запросы space, drama и неизвестное слово. Добавьте поле director и включите его в поиск. Затем создайте select с жанром и вычисляйте список сразу по query и выбранному genre. Добавьте кнопку очистки, которая вызывает setQuery(""). После этого отсортируйте результат по году через копию массива. Убедитесь, что исходный movies не меняется. Последний шаг: вынесите счётчик и пустое состояние в маленькие компоненты только после того, как код работает.
- Проверьте весь список без запроса.
- Введите слово в другом регистре.
- Добавьте пробелы по краям запроса.
- Получите нулевой результат.
- Добавьте фильтр по жанру.
- Очистите поиск одной кнопкой.
- Карточки строятся через map.
- Поиск работает по двум полям.
- Пустое состояние видно.
- Состояние хранит только query.
Интерфейс остаётся предсказуемым, когда данные не исправляются вручную после каждого события. После этой проверки измените одно правило React и Vite и заранее запишите ожидаемый результат. Так копирование превращается в понимание.

Четыре проверки перед следующим шагом. Пустой результат является штатным состоянием
Куда развивать первый React-проект
Следующим шагом добавьте React Router и отдельную страницу фильма, затем сохраните избранное в localStorage. Внешний API подключайте после появления состояния loading, error и retry. Для форм используйте обычные контролируемые поля, пока не почувствуете повторение. TypeScript можно добавить через шаблон react-ts или перенести проект позже. Перед публикацией выполните npm run build и проверьте dist через npm run preview, а не только dev-сервер.
Как изучить тему в Кодике
Повторите массивы, filter, map, события и функции JavaScript, затем свяжите их с компонентами. В Кодике сначала восстановите чистую логику без фреймворка, затем верните библиотеку и интерфейс React и Vite.
| Шаг | Что разобрать | Мини-проверка |
|---|---|---|
| 1 | Массивы объектов | Найти фильмы через filter |
| 2 | Функции и props | Передать movie в карточку |
| 3 | События input | Получить event.target.value |
| 4 | Состояние | Очистить query кнопкой |
| 5 | Компоненты React | Добавить фильтр жанра |
Если вы можете написать filter без React, код компонента перестаёт выглядеть отдельным языком. Финальная проверка для React и Vite: закрыть статью, объяснить путь данных и добавить одну функцию без копирования готового блока.
Вычисляйте список из movies и query, иначе появятся две версии одних данных.
Берите стабильный movie.id, особенно перед сортировкой и удалением.
Сначала создайте копию массива.
Сначала доведите локальный пользовательский сценарий.
Что получится в итоге

Поле поиска мгновенно фильтрует каталог, обновляет счётчик и показывает пустое состояние без ручного изменения DOM.
Зачем React нужен key в списке?
key связывает элемент интерфейса со стабильной записью данных между рендерами.
Нужно ли хранить visibleMovies в useState?
Нет, visibleMovies вычисляется из movies и query и не является отдельным источником истины.
Что делает Vite в React-проекте?
Vite запускает dev-сервер, обрабатывает модули и собирает оптимизированный production-бандл.
Как понять, что учебный проект React и Vite действительно работает?
Проверьте основной сценарий React и Vite, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.
Можно ли начать с готового кода React и Vite из статьи?
Да. Сначала добейтесь результата React и Vite, затем измените одно правило и восстановите ключевой файл без копирования.
JavaScript и работу с массивами закрепите в курсе JavaScript, а разметку карточек в HTML и CSS.
Сравните состояние с todo-листом на JavaScript, типы с первым проектом TypeScript, а запросы к сети с Fetch API.