State: внутреннее состояние

State (состояние) компонента — данные, которые компонент хранит и изменяет сам; при изменении state React перерендеривает компонент с новыми значениями.

Зачем нужно

Без state компоненты были бы чисто статическими шаблонами. State — механизм «памяти» компонента: он помнит, открыт ли дропдаун, что пользователь ввёл в поле, сколько элементов в корзине. Понимание того, что изменение state запускает перерендер, — ключ к работе с React и отладке неожиданного поведения.

Где используется

  • Значение инпута, текстового поля
  • Открыт/закрыт: модалки, аккордеоны, дропдауны, тултипы
  • Текущий шаг многошагового процесса (wizard, onboarding)
  • Данные, загружаемые с сервера и отображаемые в компоненте
  • Любые UI-флаги: isLoading, isError, isExpanded

useState: базовое состояние

import { useState } from 'react';

function Counter() {
  // useState возвращает [текущее значение, функция-сеттер]
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Счётчик: {count}</p>
      {/* Функциональное обновление — безопасно при асинхронности */}
      <button onClick={ => setCount(prev => prev + 1)}>+</button>
      <button onClick={ => setCount(prev => prev - 1)}>-</button>
      <button onClick={ => setCount(0)}>Сброс</button>
    </div>
  );
}

function ToggleButton() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <div>
      <button onClick={ => setIsOpen(prev => !prev)}>
        {isOpen ? 'Закрыть' : 'Открыть'}
      </button>
      {isOpen && <div className="dropdown">Содержимое</div>}
    </div>
  );
}

Состояние объекта

function ProfileForm() {
  const [form, setForm] = useState({
    name: '',
    email: '',
    bio: '',
  });

  // Важно: spread предыдущего состояния — не мутировать напрямую
  const handleChange = (field) => (e) => {
    setForm(prev => ({
      ...prev,
      [field]: e.target.value,
    }));
  };

  return (
    <form>
      <input value={form.name} onChange={handleChange('name')} placeholder="Имя" />
      <input value={form.email} onChange={handleChange('email')} placeholder="Email" />
      <textarea value={form.bio} onChange={handleChange('bio')} placeholder="О себе" />
    </form>
  );
}

useReducer — сложное состояние

import { useReducer } from 'react';

const initialState = { count: 0, step: 1 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { ...state, count: state.count + state.step };
    case 'decrement':
      return { ...state, count: state.count - state.step };
    case 'setStep':
      return { ...state, step: action.payload };
    case 'reset':
      return initialState;
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Счётчик: {state.count} (шаг: {state.step})</p>
      <button onClick={ => dispatch({ type: 'increment' })}>+</button>
      <button onClick={ => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={ => dispatch({ type: 'reset' })}>Сброс</button>
      <input
        type="number"
        value={state.step}
        onChange={(e) => dispatch({ type: 'setStep', payload: Number(e.target.value) })}
      />
    </div>
  );
}

Частые ошибки

  • Прямая мутация statestate.items.push(item) не вызывает перерендер; всегда возвращайте новый объект через spread или map/filter.
  • State для данных, которые можно вычислить — если значение зависит от другого state, вычисляйте его в рендере, не дублируйте в отдельный state.
  • Избыточное состояние — хранить в state форматированную дату, если в state уже есть Date-объект — дублирование.
  • Асинхронное чтение state — после setCount(count + 1) переменная count в том же обработчике ещё старая; используйте функциональное обновление.

Связанные темы

Ресурсы