Skip to content

События и состояние React

Добавляем счетчик и фильтр, разбираем очередь обновлений.

On this page

Для Next.js 16.3.8 (App Router) и React 19.3.0. Обновлено .

В обычном JavaScript изменение переменной само по себе не обновляло DOM: мы вызывали render. В React состояние хранится через useState. Функция обновления состояния сообщает React, что интерфейс нужно построить заново с новым значением.

useState – хук, функция React для подключения возможностей компонента. Хуки вызываются на верхнем уровне компонента, до условных return, а не внутри if, циклов или обработчиков.

Счетчик и его подключение

Создайте клиентский компонент:

app/ticket-counter.tsx
"use client";
 
import { useState } from "react";
 
export function TicketCounter() {
  const [count, setCount] = useState(1);
 
  return (
    <div>
      <p>Билетов: {count}</p>
      <button type="button" onClick={() => setCount((current) => current + 1)}>
        Добавить билет
      </button>
    </div>
  );
}

useState(1) задает начальное значение. Деструктуризация массива дает два элемента: count – текущее значение, setCount – функция обновления. Названия выбирает разработчик.

onClick получает функцию, которую React вызовет после нажатия. Запись onClick={setCount(2)} вызвала бы обновление сразу при отображении; передавайте функцию, например onClick={() => setCount(2)}.

Подключите счетчик:

app/page.tsx
import { TicketCounter } from "./ticket-counter";
 
export default function HomePage() {
  return (
    <main>
      <h1>Куда пойти</h1>
      <TicketCounter />
    </main>
  );
}

"use client" нужна компоненту с состоянием и событиями. Страница может оставаться серверной: подробнее разберем эту границу во втором этапе.

Почему обычная переменная не подходит

Локальная переменная let count = 1 создается заново при каждом вызове компонента. Изменение этой переменной в обработчике не запускает обновление интерфейса. А useState сохраняет значение между вызовами компонента и дает функцию для его обновления.

Обновления попадают в очередь

Три вызова в одном нажатии

Одинаковое значение три раза

Обработчик кнопки
setCount(count + 1);setCount(count + 1);setCount(count + 1);

Первый счетчик: 0

Каждый раз берем предыдущий результат

Обработчик кнопки
setCount((current) => current + 1);setCount((current) => current + 1);setCount((current) => current + 1);

Второй счетчик: 0

Слева трижды записывается одно число. Справа каждое обновление прибавляет единицу к результату предыдущего. React выполняет эти обработчики в самой демонстрации.

В этом сравнении оба обработчика трижды вызывают обновление состояния. Нажмите кнопки и сравните результат: вариант с функцией использует результат предыдущего обновления.

setCount(count + 1) тоже допустима для одного обычного обновления. Но несколько таких вызовов в одном обработчике используют одно и то же значение count. Новое значение станет доступно при следующем вызове компонента. В уже работающем обработчике переменная останется прежней.

Фильтр без лишнего состояния

Самостоятельный пример заменяет app/page.tsx:

app/page.tsx
"use client";
 
import { useState } from "react";
 
const events = [
  { id: 1, name: "Кинопоказ", price: 300 },
  { id: 2, name: "Лекция", price: 0 },
];
 
export default function HomePage() {
  const [freeOnly, setFreeOnly] = useState(false);
  const visible = freeOnly
    ? events.filter((event) => event.price === 0)
    : events;
 
  return (
    <main>
      <button
        type="button"
        aria-pressed={freeOnly}
        onClick={() => setFreeOnly((current) => !current)}
      >
        Только бесплатные
      </button>
      {visible.length === 0 ? (
        <p>Ничего не найдено.</p>
      ) : (
        <ul>
          {visible.map((event) => (
            <li key={event.id}>{event.name}</li>
          ))}
        </ul>
      )}
    </main>
  );
}

Храним только выбор пользователя. visible вычисляется из него при отображении. Если хранить отфильтрованный список отдельно, придется обновлять и его, и фильтр. Один из них легко забыть обновить. useEffect для такого вычисления не нужен.

Объекты и массивы в состоянии

Создавайте новое значение вместо изменения существующего: setEvent((current) => ({ ...current, name: "Новое название" })). Для списка можно использовать map: вернуть новый объект для нужного элемента, а остальные оставить прежними. Простое event.name = ... не сообщает React об обновлении.

Если несколько компонентов используют один фильтр, храните его у ближайшего общего родителя и передавайте значение и обработчик через props. При удалении компонента его локальное состояние теряется; перезагрузка страницы тоже не сохраняет выбор автоматически.

Попробуйте: добавьте кнопку уменьшения количества. Не разрешайте число меньше одного. Затем вынесите фильтр в отдельный компонент: родитель хранит выбор и передает его дочернему.

Документация: события, useState, состояние как снимок, обновления в очереди.