ФронтендJavaScriptHTML / CSS

Первое приложение на React и Vite: каталог фильмов с живым поиском

Создаём React-проект через Vite, выводим карточки фильмов, фильтруем массив через state и проверяем пустой результат без внешнего API.

Кодик

Автор

7 мин чтения

React хранит строку поиска в состоянии, а отфильтрованные карточки вычисляет заново при каждом вводе. Соберём каталог из локального массива, чтобы не зависеть от ключа API. Вы увидите компонент, props, useState, map, filter и сообщение для запроса, по которому ничего не найдено.

В обычном JavaScript легко смешать данные, поиск и ручное изменение DOM. React позволяет описать, какой интерфейс соответствует текущему query. В этой статье вы соберёте адаптивный каталог с шестью фильмами и живым поиском. Финальный результат можно проверить без догадок: запрос space оставляет две карточки, неизвестное слово показывает пустое состояние, а очистка возвращает весь список.

1Данные

Массив movies остаётся обычным JavaScript и не меняется во время поиска.

2Состояние

useState хранит только то, что ввёл пользователь.

3Представление

filter и map превращают query в набор карточек без ручного innerHTML.

Как устроен проект React на Vite

Vite отвечает за dev-сервер и сборку, main.jsx монтирует корневой компонент, App.jsx хранит экран каталога. Компонент MovieCard получает один объект через props и не знает о строке поиска. App вычисляет visibleMovies из исходного массива. Для React и Vite держите этапы раздельно: ввод, проверка, основное действие и вывод. Такой маршрут позволяет остановиться в конкретной точке, увидеть фактические данные и исправить одну причину вместо случайной перестановки строк.

ЭтапДействиеПроверяемый результат
ВводonChange получает value из inputquery меняется
РендерReact повторно вызывает AppПолучено новое дерево интерфейса
Фильтрtitle и genre сравниваются с queryСобран visibleMovies
Карточкиmap создаёт MovieCard с keyНа экране только совпадения

Источник истины один: строка query. Список не копируется в отдельное состояние и не рассинхронизируется. У React и Vite каждый этап оставляет наблюдаемый след, поэтому следующий шаг начинается только после проверки предыдущего.

Поток данных React-каталога
От входных данных до видимого результата. Query является единственным изменяемым значением

Не подключайте внешний API в первом React-проекте. Сначала проверьте компонент, state и фильтрацию на локальных данных. Сеть добавляет загрузку, ошибки и лимиты, которые скрывают основу React.

Пишем каталог и живой поиск

Установите актуальный 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 специально ограничен одним пользовательским сценарием, чтобы назначение каждой строки оставалось видимым.

Ручной DOM и декларативный интерфейс
Что отличает устойчивый проект от случайного запуска. React строит представление из данных

Почему derived data не нужно хранить в state

useState возвращает текущее значение и функцию обновления. setQuery планирует новый рендер, после чего App выполняется снова. React сравнивает новое дерево с предыдущим и меняет только нужные DOM-узлы. Атрибут key связывает карточку с movie.id, поэтому React понимает идентичность элементов. Если использовать индекс массива, сортировка может перенести локальное состояние не к той карточке. Контролируемый input получает value из state, поэтому поле и query всегда совпадают.

ЧастьЗа что отвечаетЧто проверить
ViteЗапускает dev-сервер и buildnpm run dev открывает адрес
AppХранит query и фильтрует данныеНет копии visibleMovies в state
MovieCardРисует один фильмПолучает props movie
useStateХранит пользовательский вводОбновляется через setQuery
keyИдентифицирует карточкуИспользуется стабильный id

Фильтр выполняется при каждом рендере, и для шести карточек это дешевле лишнего состояния. useMemo нужен только после реального замера на большом массиве или дорогом вычислении. trim убирает пробелы по краям, toLowerCase делает сравнение нечувствительным к регистру. Для русских названий этого обычно достаточно, а сложный поиск по опечаткам является отдельной задачей. Не меняйте movies через sort на месте: методы, изменяющие массив, могут испортить исходные данные. Создавайте копию через [...movies].sort(...).

Не вызывайте setState во время рендера. Фильтрация должна быть обычным выражением. Если вызвать setQuery или setVisibleMovies прямо внутри App без события или эффекта, получится цикл рендеров.

Проверяем поиск, регистр и пустой результат

Запустите каталог и проверьте запросы space, drama и неизвестное слово. Добавьте поле director и включите его в поиск. Затем создайте select с жанром и вычисляйте список сразу по query и выбранному genre. Добавьте кнопку очистки, которая вызывает setQuery(""). После этого отсортируйте результат по году через копию массива. Убедитесь, что исходный movies не меняется. Последний шаг: вынесите счётчик и пустое состояние в маленькие компоненты только после того, как код работает.

Проверка своими руками
  1. Проверьте весь список без запроса.
  2. Введите слово в другом регистре.
  3. Добавьте пробелы по краям запроса.
  4. Получите нулевой результат.
  5. Добавьте фильтр по жанру.
  6. Очистите поиск одной кнопкой.
Готово, если выполняются все пункты
  • Карточки строятся через 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: закрыть статью, объяснить путь данных и добавить одну функцию без копирования готового блока.

Кладут filteredMovies в state

Вычисляйте список из movies и query, иначе появятся две версии одних данных.

Используют index как key

Берите стабильный movie.id, особенно перед сортировкой и удалением.

Мутируют исходный массив sort

Сначала создайте копию массива.

Подключают API до пустого состояния

Сначала доведите локальный пользовательский сценарий.

Сверяйтесь с первичной документацией. Команды и ограничения примера проверены по официальному руководству Vite. Если версия инструмента изменилась, официальный источник важнее старого скриншота.

Что получится в итоге

Готовый результат проекта: Первое приложение на React и Vite: каталог фильмов с живым поиском
Поле поиска мгновенно фильтрует каталог, обновляет счётчик и показывает пустое состояние без ручного изменения 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.