Всегда обновляй state через prev!

чт, 12 июня 2025 г. - 2 мин чтения
React-разработчик обновляет состояние через prev

⚛️ Всегда обновляй state через prev — спасёшь себя завтра

В React мы часто пишем setCount(count + 1) и радуемся, пока не поймаем гонку. Несколько обновлений подряд, асинхронные эффекты, батчинг в React 18 — и старое значение внезапно затирает новое. Привычка писать setState(prev => prev + 1) решает больше проблем, чем кажется.


10 причин использовать форму setState(prev => …)

  1. Нет гонок при последовательных обновлениях. Несколько setState подряд получают актуальную базу, даже в одном обработчике.
  2. Работает с батчингом React 18. React может отложить обновление, но prev всегда укажет на последнее значение, а не на замороженную копию.
  3. Устойчивость к асинхронности. Таймеры, fetch или await не затирают состояние, которое уже изменилось другим событием.
  4. Прозрачная логика для пары useTransition / useDeferredValue. prev хранит правду, даже когда компоненты рендерятся позже.
  5. Меньше багов в эффектах. Когда эффект меняет состояние по зависимости, callback-форма предотвращает «вечные» циклы и дублирование.
  6. Чистые аналитические счётчики. Инкременты событий не теряются при быстром клике по кнопке «лайк» или «добавить в корзину».
  7. Предсказуемый undo/redo. Когда состояние стековое, prev помогает строить историю без пропусков.
  8. Безопасные обновления сложных структур. Обновляя массивы и объекты на основе prev, проще писать иммутабельный код и избежать мутаций.
  9. Совместимость с конкурентным режимом. В будущем React может перерендерить компонент несколько раз; callback гарантирует, что расчёт идёт от актуальной версии.
  10. Лёгкая поддержка для будущего себя. Коллеге (или вам через месяц) проще понять намерение, когда видно, что новое значение зависит от предыдущего.

Как превратить prev в привычку

  • Начинайте с простого: заменяйте setValue(value + 1) на setValue(prev => prev + 1) в езде. Даже если кажется, что гонки не будет.
  • Обновляйте сложные объекты: setFilters(prev => ({ ...prev, page: prev.page + 1 })) — и никаких мутаций.
  • Используйте ESLint-правила: подключите eslint-plugin-react-hooks/exhaustive-deps и собственное правило, которое ругается на обновление без prev.
  • Покрывайте тестами: пишите юнит-тесты, которые вызывают setState несколько раз подряд, чтобы убедиться: значение растёт.
  • Документируйте подход: добавьте в contributing-guide раздел про callback-обновления и почему они обязательны.

Пример, который ломается без prev

const [likes, setLikes] = useState(0);
 
const handleLike = () => {
  setLikes(prev => prev + 1);
  sendAnalyticsEvent('like_clicked');
};

Если заменить на setLikes(likes + 1), быстрые клики или deferred-render легко «съедят» пару инкрементов. Колбэк же аккуратно складывает каждое действие.


Итог

Callback-форма обновления состояния — страховка от тишком ползущих багов. Потратьте пару минут на рефакторинг сегодня, и завтра спасибо скажут и метрики, и QA, и вы, когда будете разбирать, почему «счётчик снова сломался». Сделайте prev привычным жестом — и React-компоненты станут надёжнее. 💪