Skip to content

Commit

Permalink
createBatch reference doc rewrite (#982)
Browse files Browse the repository at this point in the history
  • Loading branch information
edemaine authored Dec 18, 2024
1 parent cba901d commit d2978b5
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/routes/reference/basic-reactivity/create-effect.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ createEffect((prevSum) => {
```

Effects are meant primarily for side effects that read but don't write to the reactive system: it's best to avoid setting signals in effects, which without care can cause additional rendering or even infinite effect loops. Instead, prefer using [createMemo](/reference/basic-reactivity/create-memo) to compute new values that depend on other reactive values, so the reactive system knows what depends on what, and can optimize accordingly.
If you do end up setting a signal within an effect, computations subscribed to that signal will be executed only once the effect completes; see [`batch`](/reference/reactive-utilities/batch) for more detail.

The first execution of the effect function is not immediate; it's scheduled to run after the current rendering phase (e.g., after calling the function passed to [render](/reference/rendering/render), [createRoot](/reference/reactive-utilities/create-root), or [runWithOwner](/reference/reactive-utilities/run-with-owner)).
If you want to wait for the first execution to occur, use [queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask) (which runs before the browser renders the DOM) or `await Promise.resolve()` or `setTimeout(..., 0)` (which runs after browser rendering).
Expand Down
90 changes: 86 additions & 4 deletions src/routes/reference/reactive-utilities/batch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,89 @@ import { batch } from "solid-js"
function batch<T>(fn: () => T): T
```

This is a low level API that is used by Solid to batch updates.
It holds executing downstream computations within the block until the end to prevent unnecessary recalculation.
Solid Store's set method, Mutable Store's array methods, and Effects automatically wrap their code in a batch.
This is useful for when you want to batch a set of computations that are not wrapped in a batch already.
`batch` is a low-level API that batches updates together.
More precisely, `batch(fn)` holds the execution of downstream computations
during the `fn` block, executing them all together once the block `fn` returns.
Thus, instead of a downstream computation executing after every dependency
update, it will update just once at the end of the batch.

Batching improves performance by avoiding unnecessary recalculation.
Suppose you have a downstream memo `down` that depends on
multiple upstream signals `up1`, `up2`, and `up3`:

```ts
import { createSignal, createMemo, createEffect } from "solid-js"
const [up1, setUp1] = createSignal(1)
const [up2, setUp2] = createSignal(2)
const [up3, setUp3] = createSignal(3)
const down = createMemo(() => up1() + up2() + up3())
// For illustration, monitor when `down` gets recomputed:
createEffect(() => console.log(down())) // outputs 6
```

If you directly update all of the upstream signals outside of batch mode,
then `down` will recompute every time.

```ts
setUp1(4) // recomputes down, outputs 9
setUp2(5) // recomputes down, outputs 12
setUp3(6) // recomputes down, outputs 15
```

If instead you update the upstream signals within a `batch`, then `down`
will update only once at the end:

```ts
batch(() => {
setUp1(10) // doesn't update down yet
setUp2(10) // doesn't update down yet
setUp3(10) // doesn't update down yet
}) // recomputes down, outputs 30
```

The impact is even more dramatic if you have *m* downstream computations
(memos, effects, etc.) that each depend on *n* upstream signals.
Without batching, modifying all *n* upstream signals
would cause *m n* updates to the downstream computations.
With batching, modifying all *n* upstream signals
would cause *m* updates to the downstream computations.
Given that each update takes at least *n* time
(just to read the upstream signals), this cost savings can be significant.
Batching is also especially helpful when the downstream effects include
DOM updates, which can be expensive.

Solid uses `batch` internally to automatically batch updates for you
in a few cases:

* Within [`createEffect`](/reference/basic-reactivity/create-effect)
and [`onMount`](/reference/lifecycle/on-mount)
(unless they are outside a [root](/reference/reactive-utilities/create-root))
* Within the [setter of a store](/reference/store-utilities/create-store#setter)
(which can update several properties at once)
* Within array methods (e.g. `Array.prototype.splice`) of a
[mutable store](/reference/store-utilities/create-mutable)
(which can update several elements at once)

These save you from having to use `batch` yourself in many cases.
For the most part, automatic batching should be transparent to you,
because accessing a signal or memo will cause it to update if it is out of date
(as of Solid 1.4). For example:

```ts
batch(() => {
setUp1(11) // doesn't update down yet
setUp2(11) // doesn't update down yet
setUp3(11) // doesn't update down yet
console.log(down()) // recomputes down, outputs 33
setUp1(12) // doesn't update down yet
setUp2(12) // doesn't update down yet
setUp3(12) // doesn't update down yet
}) // recomputes down, outputs 36
```

You can think of `batch(fn)` as setting a global "batch mode" variable,
calling the function `fn`, and then restoring the global variable to its
previous value.
This means that you can nest `batch` calls, and they will form one big batch.
It also means that, if `fn` is asynchronous,
only the updates before the first `await` will be batched.

0 comments on commit d2978b5

Please sign in to comment.