batch

Groups multiple signal updates to trigger effects only once.

API

function batch<T>(fn: () => T): T
  • fn: A function that contains the signal updates to be batched.
  • Returns: The value returned by the function fn.

TypeScript

The batch function is generic and preserves the return type of the function it wraps.

type BatchFunction<T> = () => T;

const result: string = batch(() => {
  // ... signal updates
  return "done";
});

Basic Usage

Without batch, each signal update triggers its own effect run. With batch, all updates are grouped, and effects run only once.

import { signal, batch, effect } from '@hellajs/core';

const firstName = signal("John");
const lastName = signal("Doe");
let updateCount = 0;

effect(() => {
  console.log(`${firstName()} ${lastName()}`);
  updateCount++;
});
// Initial run: logs "John Doe", updateCount is 1

// Without batching: 2 separate effect runs
firstName("Jane");   // Logs "Jane Doe"
lastName("Smith"); // Logs "Jane Smith"
console.log(updateCount); // 3

// With batching: 1 effect run for both updates
batch(() => {
  firstName("Alice");
  lastName("Johnson");
}); // Logs "Alice Johnson"
console.log(updateCount); // 4 (only 1 additional update)

Key Concepts

Update Deferral

Batch defers all reactive updates until the batch function completes, preventing intermediate state inconsistencies and reducing unnecessary computations.

const a = signal(1);
const b = signal(2);
effect(() => console.log(a() + b()));

batch(() => {
  a(10); // Effect doesn't run yet
  b(20); // Effect doesn't run yet
}); // Effect runs once here: logs 30

Synchronous Boundaries

Batching only applies to synchronous signal updates within the batch function - async operations break batch boundaries.

batch(() => {
  signal1('value1'); // Batched
  setTimeout(() => signal2('value2'), 0); // Not batched
  signal3('value3'); // Batched with signal1
});

Performance Optimization

Reduces the number of effect executions and DOM updates when multiple related signals change simultaneously.

// Without batch: 3 effect runs
name('John'); // Effect runs
age(30); // Effect runs  
email('john@example.com'); // Effect runs

// With batch: 1 effect run
batch(() => {
  name('John');
  age(30);
  email('john@example.com');
}); // Effect runs once

Nested Batch Behavior

Nested batches are flattened - effects only run once the outermost batch completes, regardless of nesting depth.

batch(() => {
  signal1('a');
  batch(() => {
    signal2('b'); // Still part of outer batch
  });
  signal3('c');
}); // All effects run together at the end

Important Considerations

Async Operations

Batching only applies to synchronous signal updates within the batch function. Asynchronous operations break batch boundaries.

// ❌ Async operations break batching
batch(() => {
  signal1('value');
  setTimeout(() => signal2('delayed'), 0);
});
// ✅ Batch synchronous operations separately
batch(() => { signal1('value'); signal2('value'); });

Nested Batching

Batches can be nested, but effects only run when the outermost batch completes.

// ✅ Nested batches are flattened
batch(() => {
  signal1('a');
  batch(() => signal2('b'));
  signal3('c');
}); // Effects run once at the end