@rbxts/signals
batch
Groups multiple signal updates together to prevent unnecessary recalculations.
Signature
function batch<T>(fn: () => T): T;
Description
The batch()
function groups multiple signal updates together, ensuring that computations (effects and memos) only run once after all updates are complete rather than after each individual update. This optimization significantly improves performance when multiple reactive values need to be updated simultaneously.
Nested batches are supported but behave as a single batch. The createEffect
and createMemo
functions are already internally batched, so explicit batching is typically only needed when directly updating signals.
Important Behavior
During a batch operation, signal values are not immediately updated when reading them:
- Reading a signal during a batch returns the frozen value from before the batch, not the pending value
- Only the
.set()
method has access to the pending value during the batch - All values are updated and effects run only after the batch completes
This behavior allows for consistent reading of values during a batch operation, but requires careful handling when you need to access updated values within the batch itself.
Examples
Basic usage
const firstName = createSignal("John");const lastName = createSignal("Doe");const age = createSignal(30);
// This effect will run on any change to the signalscreateEffect(() => { print(`${firstName()} ${lastName()}, ${age()} years old`);});// Prints: "John Doe, 30 years old" (initial run)
// Without batching - would run the effect 3 timesfirstName("Jane");lastName("Smith");age(25);// Prints effect 3 separate times
// With batching - effect runs only once after all updatesbatch(() => { firstName("Robert"); lastName("Johnson"); age(40);});// Prints effect only once: "Robert Johnson, 40 years old"
Signal values during a batch
const count = createSignal(0);
batch(() => { // Update the count to 1 count(1);
// Reading count() still returns 0 during the batch print(count()); // 0
// Using set() with a function gives access to the pending value count.set(current => { print(`Current pending value: ${current}`); // 1 return current + 1; });
// Even after set(), reading count() still returns the original value print(count()); // 0});
// After the batch completes, reading count() returns the final valueprint(count()); // 2
With complex updates
const items = createSignal<string[]>([]);const count = createSignal(0);const loading = createSignal(true);
createEffect(() => { print(`Items: ${items().join(", ")}, Count: ${count()}, Loading: ${loading()}`);});// Prints: "Items: , Count: 0, Loading: true" (initial run)
// Group all these updates into one batchbatch(() => { items(["Apple", "Banana", "Cherry"]); count(3); loading(false);});// Prints effect only once: "Items: Apple, Banana, Cherry, Count: 3, Loading: false"
Nested batches
const a = createSignal(1);const b = createSignal(2);const c = createSignal(3);
createEffect(() => { print(`Sum: ${a() + b() + c()}`);});// Prints: "Sum: 6" (initial run)
batch(() => { a(10);
// Nested batch - treated as part of the outer batch batch(() => { b(20); c(30); });
// Still inside the outer batch a(100);});// Prints effect only once: "Sum: 150"