Mengantre Serangkaian Pembaruan State
Mengatur variabel state akan menambahkan antrean (queue) render baru. Terkadang Anda ingin melakukan beberapa operasi terhadap nilai sebelum menambahkan antrean render selanjutnya. Untuk melakukannya, penting untuk memahami bagaimana React melakukan pembaruan state secara berkelompok.
You will learn
- Apa itu “pengelompokan (batching)” dan bagaimana React menggunakannya untuk memproses beberapa pembaruan state
- Bagaimana menerapkan beberapa pembaruan ke variabel state yang sama secara berurutan
Mengelompokkan pembaruan state dalam React
Anda mungkin berharap bahwa menekan tombol “+3” akan menambahkan hitungan tiga kali karena memanggil setNumber(number + 1)
tiga kali:
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 1); setNumber(number + 1); setNumber(number + 1); }}>+3</button> </> ) }
Namun, seperti yang mungkin Anda ingat dari bagian sebelumnya, nilai state setiap render adalah tetap, sehingga nilai number
di dalam event handler render pertama selalu 0
, tidak peduli berapa kali Anda memanggil setNumber(1)
:
```js
setNumber(0 + 1);
setNumber(0 + 1);
setNumber(0 + 1);
Akan tetapi, ada satu faktor lain yang berperan di sini. React menunggu sampai semua kode dalam event handler selesai dijalankan sebelum memproses pembaruan state Anda. Inilah sebabnya mengapa re-render hanya terjadi setelah semua setNumber()
dipanggil.
Ini mungkin mengingatkan Anda pada seorang pelayan yang menerima pesanan di restoran. Seorang pelayan tidak berlari ke dapur saat Anda menyebutkan hidangan pertama Anda! Sebaliknya, mereka membiarkan Anda menyelesaikan pesanan Anda, membiarkan Anda mengubahnya, dan bahkan menerima pesanan dari orang lain di meja tersebut.

Illustrated by Rachel Lee Nabors
Ini memungkinkan Anda memperbarui beberapa variabel state—bahkan dari beberapa komponen—tanpa memicu terlalu banyak re-render. Akan tetapi, hal ini ini membuat UI tidak akan diperbarui hingga setelah event handler Anda, dan kode apa pun di dalamnya, selesai dijalankan. Perilaku ini, juga dikenal sebagai pengelompokan, membuat aplikasi React Anda berjalan lebih cepat. Ini juga menghindari penanganan render “setengah jadi” yang membingungkan ketika hanya beberapa variabel yang diperbarui.
React tidak melakukan pengelompokkan pada beberapa event yang disengaja, seperti klik—setiap klik ditangani secara terpisah. Pastikan bahwa React hanya melakukan pengelompokan ketika aman untuk dilakukan. Ini memastikan bahwa, misalnya, jika klik tombol pertama menonaktifkan form, klik kedua tidak akan mengirimkannya lagi.
Memperbarui state yang sama beberapa kali sebelum render selanjutnya
It is an uncommon use case, but if you would like to update the same state variable multiple times before the next render, instead of passing the next state value like setNumber(number + 1)
, you can pass a function that calculates the next state based on the previous one in the queue, like setNumber(n => n + 1)
. It is a way to tell React to “do something with the state value” instead of just replacing it.
Ini bukanlah penggunaan yang umum, tetapi jika Anda ingin memperbarui variabel state yang sama berulang kali sebelum render selanjutnya, alih-alih mengoper nilai state selanjutnya seperti setNumber(number + 1)
, Anda dapat mengoper function yang menghitung state selanjutnya berdasarkan nilai sebelumnya pada antrean, seperti setNumber(n => n + 1)
. Ini adalah cara untuk memberi tahu React untuk “melakukan sesuatu dengan nilai state” daripada hanya menggantinya.
Cobalah untuk menambahkan hitungan sekarang:
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(n => n + 1); setNumber(n => n + 1); setNumber(n => n + 1); }}>+3</button> </> ) }
Disini, n => n + 1
disebut fungsi updater. Ketika Anda mengirimkannya ke pengatur (setter) state:
- React mengantre fungsi ini untuk diproses setelah semua kode lain dalam event handler dijalankan.
- Saat render berikutnya, React akan melewati antrean dan memberi Anda state terakhir yang diperbarui.
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
Berikut adalah cara kerja React melalui baris kode ini saat menjalankan event handler:
setNumber(n => n + 1)
:n => n + 1
is a function. React adds it to a queue.setNumber(n => n + 1)
:n => n + 1
is a function. React adds it to a queue.setNumber(n => n + 1)
:n => n + 1
is a function. React adds it to a queue.
Ketika Anda memanggil useState
saat render berikutnya, React akan melewati antrean. State number
sebelumnya adalah 0
, jadi itulah yang akan diteruskan React ke fungsi updater pertama sebagai argumen n
. Kemudian React mengambil hasil dari fungsi updater sebelumnya dan meneruskannya ke updater berikutnya sebagai n
, dan begitu seterusnya:
antrean diperbarui | n | hasil |
---|---|---|
n => n + 1 | 0 | 0 + 1 = 1 |
n => n + 1 | 1 | 1 + 1 = 2 |
n => n + 1 | 2 | 2 + 1 = 3 |
React menyimpan 3
sebagai hasil akhir dan mengembalikannya dari useState
.
Inila mengapa mengklik “+3” pada contoh di atas dengan benar meningkatkan nilai sebesar 3.
Apa yang terjadi jika Anda memperbarui state setelah menggantinya
Bagaimana dengan event handler ini? Menurut Anda berapa nilai number
pada render berikutnya?
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
}}>
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 5); setNumber(n => n + 1); }}>Increase the number</button> </> ) }
Begini cara event handler memberitahu React apa yang harus dilakukan:
setNumber(number + 5)
:number
adalah0
, jadisetNumber(0 + 5)
. React menambahkan “ganti dengan5
” ke antreannya.setNumber(n => n + 1)
:n => n + 1
merupakan fungsi updater. React menambahkan fungsi tersebut ke antreannya.
Selama render berikutnya, React melewati antrean state:
antrean diperbarui | n | hasil |
---|---|---|
“ganti dengan 5 ” | 0 (tak terpakai) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
React menyimpan 6
sebagai hasil akhir dan mengembalikannya dari useState
.
What happens if you replace state after updating it
Let’s try one more example. What do you think number
will be in the next render?
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(42);
}}>
import { useState } from 'react'; export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(number + 5); setNumber(n => n + 1); setNumber(42); }}>Increase the number</button> </> ) }
Here’s how React works through these lines of code while executing this event handler:
setNumber(number + 5)
:number
is0
, sosetNumber(0 + 5)
. React adds “replace with5
” to its queue.setNumber(n => n + 1)
:n => n + 1
is an updater function. React adds that function to its queue.setNumber(42)
: React adds “replace with42
” to its queue.
During the next render, React goes through the state queue:
queued update | n | returns |
---|---|---|
“replace with 5 ” | 0 (unused) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
“replace with 42 ” | 6 (unused) | 42 |
Then React stores 42
as the final result and returns it from useState
.
To summarize, here’s how you can think of what you’re passing to the setNumber
state setter:
- An updater function (e.g.
n => n + 1
) gets added to the queue. - Any other value (e.g. number
5
) adds “replace with5
” to the queue, ignoring what’s already queued.
After the event handler completes, React will trigger a re-render. During the re-render, React will process the queue. Updater functions run during rendering, so updater functions must be pure and only return the result. Don’t try to set state from inside of them or run other side effects. In Strict Mode, React will run each updater function twice (but discard the second result) to help you find mistakes.
Naming conventions
It’s common to name the updater function argument by the first letters of the corresponding state variable:
setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);
If you prefer more verbose code, another common convention is to repeat the full state variable name, like setEnabled(enabled => !enabled)
, or to use a prefix like setEnabled(prevEnabled => !prevEnabled)
.
Recap
- Setting state does not change the variable in the existing render, but it requests a new render.
- React processes state updates after event handlers have finished running. This is called batching.
- To update some state multiple times in one event, you can use
setNumber(n => n + 1)
updater function.
Challenge 1 of 2: Fix a request counter
You’re working on an art marketplace app that lets the user submit multiple orders for an art item at the same time. Each time the user presses the “Buy” button, the “Pending” counter should increase by one. After three seconds, the “Pending” counter should decrease, and the “Completed” counter should increase.
However, the “Pending” counter does not behave as intended. When you press “Buy”, it decreases to -1
(which should not be possible!). And if you click fast twice, both counters seem to behave unpredictably.
Why does this happen? Fix both counters.
import { useState } from 'react'; export default function RequestTracker() { const [pending, setPending] = useState(0); const [completed, setCompleted] = useState(0); async function handleClick() { setPending(pending + 1); await delay(3000); setPending(pending - 1); setCompleted(completed + 1); } return ( <> <h3> Pending: {pending} </h3> <h3> Completed: {completed} </h3> <button onClick={handleClick}> Buy </button> </> ); } function delay(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); }