Menu

Хук useState в React: примеры и частые ошибки

useState даёт компоненту значение, которое переживает повторные рендеры, и функцию-сеттер, которая перерисовывает экран при изменении значения. Синтаксис, функции обновления, объекты и массивы в состоянии и почему значение выглядит устаревшим сразу после установки.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

useState это хук, который позволяет компоненту запоминать что-то между рендерами: счётчик, текст в поле ввода, открыто ли меню. Каждый раз, когда вы вызываете его сеттер, React снова рендерит компонент с новым значением.

Нажмите кнопку, затем замените useState(0) на useState(10) в редакторе. Превью пересоберётся, и счёт начнётся с 10.

Синтаксис

const [value, setValue] = useState(initialValue);
  • initialValue используется только при первом рендере. При каждом следующем рендере React его игнорирует и возвращает сохранённое значение.
  • value это текущее значение для этого рендера.
  • setValue заменяет значение и планирует новый рендер.

Имена выбираете вы. useState возвращает массив из двух элементов, а квадратные скобки распаковывают его, поэтому [isOpen, setIsOpen] и [email, setEmail] это один и тот же шаблон.

Вызывайте useState на верхнем уровне компонента, никогда внутри цикла, условия или вложенной функции. React сопоставляет каждый вызов с его сохранённым значением по порядку вызовов, поэтому этот порядок должен быть одинаковым при каждом рендере (см. правила хуков).

Несколько значений состояния

Компонент может вызывать useState столько раз, сколько нужно. Каждый вызов это отдельное независимое значение.

Состояние как снимок

Сеттер не меняет переменную, которая у вас в руках. Он сообщает React, каким должно быть значение в следующем рендере. Пока этот рендер не произошёл, текущий продолжает видеть старое значение.

Откройте консоль под превью и нажмите один раз. Обработчик выводит 0, старое значение, а затем компонент сообщает о новом рендере с 1. Ошибки здесь нет: count это константа на весь рендер, а следующий рендер получает свежую.

Функции обновления

Из-за снимка трёхкратный вызов сеттера с count + 1 добавляет один, а не три: каждый вызов вычисляет 0 + 1. Передайте вместо этого функцию, и React будет подавать каждому вызову результат предыдущего.

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

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

React решает, рендерить ли снова, сравнивая старое и новое значение через Object.is. Если вы меняете объект на месте и передаёте тот же объект обратно, сравнение говорит, что ничего не изменилось, и экран остаётся прежним. Всегда передавайте новый объект или массив.

{ ...user, age: user.age + 1 } копирует все поля и переопределяет одно. [...tags, 'code'] добавляет элемент, а filter удаляет, оба возвращают новый массив. Страница об обновлении массивов и объектов разбирает вложенные объекты, изменение одного элемента списка и вставку в середину.

Ленивое начальное значение

Аргумент useState вычисляется при каждом рендере, хотя React использует его только в первый раз. Когда вычисление дорогое (чтение localStorage, разбор большой строки), передайте функцию. React вызовет её один раз, при первом рендере.

// Runs readSavedNotes() on every render, then throws the result away
const [notes, setNotes] = useState(readSavedNotes());

// Runs it once
const [notes, setNotes] = useState(readSavedNotes);
const [notes, setNotes] = useState(() => readSavedNotes());

Сброс состояния через key

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

Напишите сообщение, затем смените получателя: черновик очистится. Удалите key={to} и попробуйте снова, и текст останется, потому что теперь React видит тот же Draft на том же месте.

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

Вывод состояния сразу после установки. Вы увидите старое значение, как показано выше. Выводите в теле компонента или сначала вычислите следующее значение в переменную и используйте её.

Мутация состояния. user.age++; setUser(user) передаёт тот же объект, поэтому рендера не будет. Сначала скопируйте.

Вызов сеттера во время рендера. <button onClick={setCount(count + 1)}> вызывает setCount во время рендеринга, что вызывает новый рендер, который снова его вызывает. React останавливает этот цикл ошибкой "Too many re-renders". Передайте функцию: onClick={() => setCount(count + 1)}.

Хранение значений, которые можно вычислить. Если fullName всегда равен first + ' ' + last, вычисляйте его во время рендера, а не храните третье значение состояния, которое может рассинхронизироваться.

Часто задаваемые вопросы

Что возвращает useState?

Массив ровно из двух элементов: текущее значение и функцию, которая его заменяет. Имена вы задаёте сами через деструктуризацию массива, по соглашению [thing, setThing].

Почему состояние не обновляется сразу?

Вызов сеттера не меняет переменную, которая у вас уже есть. Он просит React снова отрендерить компонент, и новое значение появится в этом следующем рендере. Внутри текущего обработчика события в области видимости остаётся старое значение, поэтому console.log(count) сразу после setCount(count + 1) выводит старое число.

Когда передавать в сеттер функцию?

Когда новое значение зависит от старого и вы устанавливаете его больше одного раза или изнутри таймера или эффекта. setCount(c => c + 1) всегда начинает с последнего значения, а setCount(count + 1) начинает со значения, которое видел этот рендер.

Может ли useState хранить объект или массив?

Да, но считайте его доступным только для чтения. Создайте новый объект или массив и передайте его в сеттер (setUser({ ...user, name }), setItems([...items, item])). Изменение существующего на месте не вызывает рендер.

Чем useState отличается от useReducer?

Оба хранят состояние. useState подходит для нескольких независимых значений. useReducer переносит логику обновления в одну функцию, которая получает действие, и это читается лучше, когда много событий меняют одно и то же состояние связанными способами.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

НАЧАТЬ