Batching

You may want to modify multiple observables at once without triggering callbacks for each change. Batching postpones renders and listeners until the end of the batch.

Batching can be done in two ways, wrapping between beginBatch() and endBatch() or in a callback with batch(callback).

import { batch, beginBatch, endBatch } from "@legendapp/state"

// Wrap in begin and end
beginBatch()
doManyChanges()
endBatch()

// Or batch with a callback
batch(() => {
    doManyChanges()
})

When to batch

As we all know, you generally shouldn't optimize pre-emptively. observable already batches changes under the hood when making modifications, so listeners don't get called until the full change is complete.

Batching is important in a few key situations:

Observables depend on each other

Use batch to delay computations/renders until all dependent changes are complete.

const name = observable({ first: '', last: '' })

const fullName = computed(() => `${name.first} ${name.last}`)

observe(() => console.log('fullName = ', fullName.get()))

// ❌ fullName computes with incomplete state
// fullName = "First "
name.first.set('First')

// ✅ fullName computes with final state
// fullName = "First Last"
batch(() => {
    name.first.set('First')
    name.last.set('Last')
})

Prevent excessive renders

Making multiple changes in a row can cause the React hook to re-render multiple times when it should wait until changes are complete.

const name = observable({ items: [] })

function addItems() {
    for (let i = 0; i < 1000; i ++) {
        obs.items.push({ text: `Item ${i}` })
    }
}

// ❌ This can render 1000 times while pushing to the array
addItems()

// ✅ Batching delays until complete and renders once
batch(addItems)

When persisting

If you are using persistObservable to automatically persist your changes, you can prevent excessive writes by delaying persistence until changes are complete. Pushing to an array 1000 times could save to storage 1000 times, which could be bad!