Skip to main content

Performance optimizations

Lazy evaluation

Atoms are lazily evaluated. If an atom is created using a state initializer function, this function won't run until the .value getter is read, or the atom is subscribed for the first time.

const $atom = atom(() => {
console.log('I am lazily evaluated!')
return expensiveComputation(25)
})
// nothing's logged on the console yet

console.log($atom.value)
// Console: "I am lazily evaluated!"
// Console: 25

console.log($atom.value)
// Console: 25

You can make use of this feature to avoid expensive computations where possible.

Lazy evaluation in derived atoms

A derived atom is not much different from a standard atom. Still, its state initializer function will wait for the atom's value to be requested in order to run.

const $alpha = atom(3)
const $beta = atom(5)

const $sum = atom((read) => {
console.log('Evaluation occurred')
return read($alpha) + read($beta)
})
// nothing's logged on the console yet

Later, when it's consumed for the first time:

console.log($sum.value)
// Console: "Evaluation occurred"
// Console: 8

console.log($sum.value)
// Console: 8

Dependency collection in derived atoms

Dependency collection is another performance optimization that makes lazy evaluation much more advanced. When an atom is evaluated, it collects its latest dependencies. Since the $sum is evaluated at least once in our previous example, it's now "aware" that its dependencies are $alpha and $beta. Let's observe what will happen when those dependencies are updated:

$alpha.set(30)
$alpha.update((s) => s + 1)
$beta.set(1000)
// nothing's logged on the console yet

console.log($sum.value)
// Console: "Evaluation occurred"
// Console: 1031

console.log($sum.value)
// Console: 1031

Observe that $sum knew that it needs to rerun its state initializer when its .value is requested after the dependencies are changed. This can happen thanks to dependency collection. $sum knows that its internal state is invalid without causing evaluation. It can avoid evaluation until it's essential.

Lazy evaluation in atoms created with .map method

Same kind of performance optimizations apply to the atoms that are created using the .map method.

const $count = atom(() => {
console.log('Ancestor atom evaluated')
return 100
})

const $doubleCount = $count.map((value) => {
console.log('Evaluation occurred')
return value * 2
})
// nothing's logged on the console yet

$count.update(s => s + 1)
// Console: "Ancestor atom evaluated"

console.log($doubleCount.value)
// Console: "Evaluation occurred"
// Console: 202

console.log($doubleCount.value)
// Console: 202

xoid supports a special kind of atoms called "stream"s. A stream is "an atom that may or may not have an immediate value". Lazy evaluation works slightly different in a "stream". See the next section for more.