Skip to main content

Кілька викликів setState

Коротка відповідь

Якщо ти викликаєш setState (або setCount, setUser тощо) кілька разів поспіль в одній події або синхронній ділянці коду, React об'єднує (batch) ці оновлення і виконує лише один ререндер.

Але! Якщо ти передаєш у setState просто значення (а не функцію), усі виклики будуть використовувати те саме старе значення стану, і тому можуть "загубитися".


1. Приклад проблеми

javascript
function Counter() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); setCount(count + 1); setCount(count + 1); }; return <button onClick={handleClick}>{count}</button>; }

Здається, що count має стати 3, але насправді він стане 1.

Чому?


2. Що робить React

React не оновлює count миттєво - він ставить усі апдейти в чергу:

javascript
setCount(count + 1)"онови count з 0 до 1" setCount(count + 1)"онови count з 0 до 1" setCount(count + 1)"онови count з 0 до 1"

Усі три апдейти використовують старе значення count = 0, тому результат після застосування - 1.


3. Правильний спосіб - використовувати функціональне оновлення

Щоб кожен виклик спирався на актуальне значення, потрібно передавати в setState функцію, яка отримує попередній стан:

javascript
function Counter() { const [count, setCount] = useState(0); const handleClick = () => { setCount(prev => prev + 1); setCount(prev => prev + 1); setCount(prev => prev + 1); }; return <button onClick={handleClick}>{count}</button>; }

Тепер кожен виклик:

javascript
1) prev = 01 2) prev = 12 3) prev = 23

Результат - 3, як і очікувалося.


4. Чому React так робить

Асинхронність оновлень

React використовує batching (пакетування) - він чекає завершення події (onClick, onChange тощо) і застосовує всі накопичені зміни одним рендером, щоб не перемальовувати компонент після кожного виклику.

Економія продуктивності

Замість 3 ререндерів React робить 1. Це в десятки разів швидше при великих деревах компонентів.


5. Після виходу з події (наприклад, setTimeout)

Іноді batching не застосовується (у старих версіях React <18) всередині асинхронних колбеків, і оновлення виконуються окремо:

javascript
setTimeout(() => { setCount(count + 1); setCount(count + 1); }, 0);

У React 18 і вище batching увімкнений навіть для асинхронного коду, тому тепер обидва оновлення теж об'єднаються.


6. Що зберігається всередині React

React веде чергу оновлень стану (updateQueue):

javascript
[ (prev) => prev + 1, (prev) => prev + 1, (prev) => prev + 1 ]

Під час рендеру він проходить по ній:

javascript
let newState = oldState; for (const update of queue) { newState = update(newState); }

Після застосування всіх апдейтів компонент ререндериться один раз.


Підсумок

СитуаціяЩо станеться
Кілька setState(value) поспільВикористовують старе значення → результат тільки останнього
Кілька setState(prev => …) поспільПрацюють послідовно → результат правильний
Усі апдейти в одній подіїОб'єднуються (batching) → 1 ререндер
React <18 (старий) у setTimeoutbatching не діє
React 18+batching працює навіть в async коді

Висновок

React не оновлює стан одразу - він накопичує зміни. Якщо потрібно оновити стан на основі попереднього, завжди використовуй функціональну форму:

javascript
setState(prev => newValueBasedOn(prev));

Це гарантує коректність, навіть при кількох викликах поспіль.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.