runInTransaction

runInTransaction() executes a function immediately inside a transaction. Reactions only flush after the outermost transaction completes, so observers never see partially-updated state. For a reusable wrapped function, use transaction().

Signature

function runInTransaction<T>(fn: () => T): T

Basic usage

import { autorun, observableBox, runInTransaction } from "@fobx/core"

const first = observableBox("Alice")
const last = observableBox("Smith")

let runs = 0
const stop = autorun(() => {
  runs++
  console.log(`${first.get()} ${last.get()}`)
})
// runs = 1, prints: Alice Smith

runInTransaction(() => {
  first.set("Bob") // deferred
  last.set("Jones") // deferred
})
// runs = 2, prints: Bob Jones
// Never sees "Bob Smith"

stop()

Return value

runInTransaction() returns whatever the body returns:

const result = runInTransaction(() => {
  x.set(1)
  y.set(2)
  return x.get() + y.get()
})
// result = 3

Nesting

Transactions nest — only the outermost one triggers the reaction flush:

runInTransaction(() => { // depth 1
  a.set(1)
  runInTransaction(() => { // depth 2
    b.set(2)
  }) // depth 2 ends — still batched
  c.set(3)
}) // depth 1 ends — flush

Error handling

If the transaction body throws, the error propagates to the caller. Pending reactions from mutations that occurred before the error are still flushed:

try {
  runInTransaction(() => {
    x.set(1) // mutation registers
    throw new Error("oops")
  })
} catch (e) {
  // x.set(1) was committed; reactions already ran
}

When to use

  • Wrapping a one-off batch of multi-property updates.
  • Event handlers that mutate several observables inline.
  • Any code path where reactions should only observe a consistent final snapshot.

Use transaction() when you want a reusable function wrapper whose every call is automatically batched.