> Почему второй setState не получает измененное значение state в React и как это исправить (React, TypeScript)

Уровень: middle · Роль: frontend · Категория: Технические вопросы

Компании: ADV/web-engineering

Стек: React, TypeScript

> Пример ответа

Короткий ответ

В React setState - асинхронная операция. При вызове второго setState сразу после первого, React ещё не применил первое изменение, поэтому второй setState работает со старым значением state. Исправляется использованием функциональной формы setState: setCount(prev => prev + 1), которая гарантирует доступ к актуальному состоянию.

Подробное объяснение

React группирует (batches) несколько вызовов setState в один ререндер для оптимизации производительности. Когда вы пишете:

JSX
setCount(count + 1);
setCount(count + 1);

Оба вызова используют одно и то же значение count из текущего рендера. React не обновляет state мгновенно - он ставит обновление в очередь и обрабатывает его позже. В результате оба setState работают с одинаковым базовым значением, и финальный state увеличивается только на 1, а не на 2.

Функциональная форма setCount(prev => prev + 1) решает проблему, так как React передаёт в callback актуальное значение state на момент применения обновления. Каждый вызов получает предыдущее значение из очереди обновлений, а не из замкнутого в замыкании.

На практике

В реальных проектах эта проблема чаще всего возникает в обработчиках событий, useEffect или setTimeout, где несколько последовательных обновлений state должны зависеть друг от друга. Типичные сценарии:

  • Инкремент счётчика несколько раз подряд
  • Toggle состояния с проверкой предыдущего значения
  • Обновление объекта или массива на основе предыдущего состояния

Решение - всегда использовать функциональную форму setState, когда новое состояние зависит от предыдущего. Это безопасно и предсказуемо независимо от того, как React группирует обновления.

В React 18 с автоматическим batching проблема менее заметна, но функциональная форма остаётся лучшей практикой для зависимых обновлений.

Пример кода

TSX
// Проблема: оба вызова используют одно и то же значение count
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(count + 1); // использует count = 0
setCount(count + 1); // тоже использует count = 0
// Результат: count = 1, а не 2
};
// Решение: функциональная форма
const handleClickFixed = () => {
setCount(prev => prev + 1); // prev = 0 → 1
setCount(prev => prev + 1); // prev = 1 → 2
// Результат: count = 2
};

Как отвечать на собеседовании

Начни с ключевой причины - асинхронность setState и batching. Объясни, что React не обновляет state мгновенно, а ставит в очередь. Покажи разницу между объектной и функциональной формой. Упомяни, что в React 18 batching улучшен, но проблема с зависимыми обновлениями остаётся. Закончи рекомендацией всегда использовать функциональную форму для зависимых обновлений.

Что проверяет интервьюер

Интервьюер оценивает понимание асинхронной природы React, механизма batching, разницы между формами setState и умение применять функциональную форму. Также проверяется знание того, как React обрабатывает очередь обновлений и почему замыкания могут давать устаревшие значения.

Типичные ошибки

  • Утверждение, что setState синхронный в обработчиках событий
  • Использование setState({...state, key: value}) без функциональной формы для зависимых обновлений
  • Предложение использовать setTimeout с нулевой задержкой как решение
  • Путаница между batching в React 17 и React 18
  • Игнорирование проблемы при работе с объектами и массивами в state

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью