Кілька викликів setState
Коротка відповідь
Якщо ти викликаєш setState (або setCount, setUser тощо) кілька разів поспіль в одній події або синхронній ділянці коду,
React об'єднує (batch) ці оновлення і виконує лише один ререндер.
Але!
Якщо ти передаєш у setState просто значення (а не функцію),
усі виклики будуть використовувати те саме старе значення стану,
і тому можуть "загубитися".
1. Приклад проблеми
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 миттєво - він ставить усі апдейти в чергу:
setCount(count + 1) → "онови count з 0 до 1"
setCount(count + 1) → "онови count з 0 до 1"
setCount(count + 1) → "онови count з 0 до 1"Усі три апдейти використовують старе значення count = 0,
тому результат після застосування - 1.
3. Правильний спосіб - використовувати функціональне оновлення
Щоб кожен виклик спирався на актуальне значення,
потрібно передавати в setState функцію, яка отримує попередній стан:
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>;
}Тепер кожен виклик:
1) prev = 0 → 1
2) prev = 1 → 2
3) prev = 2 → 3Результат - 3, як і очікувалося.
4. Чому React так робить
Асинхронність оновлень
React використовує batching (пакетування) - він чекає завершення події (onClick, onChange тощо)
і застосовує всі накопичені зміни одним рендером,
щоб не перемальовувати компонент після кожного виклику.
Економія продуктивності
Замість 3 ререндерів React робить 1. Це в десятки разів швидше при великих деревах компонентів.
5. Після виходу з події (наприклад, setTimeout)
Іноді batching не застосовується (у старих версіях React <18) всередині асинхронних колбеків, і оновлення виконуються окремо:
setTimeout(() => {
setCount(count + 1);
setCount(count + 1);
}, 0);У React 18 і вище batching увімкнений навіть для асинхронного коду, тому тепер обидва оновлення теж об'єднаються.
6. Що зберігається всередині React
React веде чергу оновлень стану (updateQueue):
[
(prev) => prev + 1,
(prev) => prev + 1,
(prev) => prev + 1
]Під час рендеру він проходить по ній:
let newState = oldState;
for (const update of queue) {
newState = update(newState);
}Після застосування всіх апдейтів компонент ререндериться один раз.
Підсумок
| Ситуація | Що станеться |
|---|---|
Кілька setState(value) поспіль | Використовують старе значення → результат тільки останнього |
Кілька setState(prev => …) поспіль | Працюють послідовно → результат правильний |
| Усі апдейти в одній події | Об'єднуються (batching) → 1 ререндер |
React <18 (старий) у setTimeout | batching не діє |
| React 18+ | batching працює навіть в async коді |
Висновок
React не оновлює стан одразу - він накопичує зміни. Якщо потрібно оновити стан на основі попереднього, завжди використовуй функціональну форму:
javascriptsetState(prev => newValueBasedOn(prev));Це гарантує коректність, навіть при кількох викликах поспіль.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.