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.
Related API
Use transaction() when you want a reusable function
wrapper whose every call is automatically batched.