From 5e45afd48e4bb652f77111cf69a468d7f98f2974 Mon Sep 17 00:00:00 2001 From: skepticattus Date: Wed, 22 Jul 2026 23:48:12 +0700 Subject: [PATCH] docs: document pattern for explicit dependency tracking across asynchronous boundaries --- documentation/docs/02-runes/03-$derived.md | 25 ++++++++++++++++++++++ documentation/docs/02-runes/04-$effect.md | 25 ++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/documentation/docs/02-runes/03-$derived.md b/documentation/docs/02-runes/03-$derived.md index f85ba90baa..a5a778af52 100644 --- a/documentation/docs/02-runes/03-$derived.md +++ b/documentation/docs/02-runes/03-$derived.md @@ -62,6 +62,31 @@ let total = $derived(await a + b); ...both `a` and `b` are tracked, even though `b` is only read once `a` has resolved, after the initial execution. (This does not apply to `await` in functions that are called by the expression, only the expression itself.) +On the other hand, values that are read _asynchronously_ (such as inside asynchronous closures passed to third-party query or observable libraries) will not be tracked automatically. To track these values, read them synchronously outside the asynchronous boundary. A clear pattern for this is to evaluate the dependencies using the `void` operator: + +```js +import { Dexie, liveQuery } from 'dexie'; + +const db = new Dexie('UserDatabase'); +// ...database setup... + +let minAge = $state(18); +let maxAge = $state(30); + +let query = $derived.by(() => { + // Read dependencies synchronously so they are tracked + void minAge, maxAge; + + return liveQuery(async () => { + // Values read inside this callback are not tracked automatically + return await db.users + .where('age') + .between(minAge, maxAge) + .toArray(); + }); +}); +``` + To exempt a piece of state from being treated as a dependency, use [`untrack`](svelte#untrack). ## Overriding derived values diff --git a/documentation/docs/02-runes/04-$effect.md b/documentation/docs/02-runes/04-$effect.md index a13fc7bc46..b7441d1764 100644 --- a/documentation/docs/02-runes/04-$effect.md +++ b/documentation/docs/02-runes/04-$effect.md @@ -107,6 +107,31 @@ $effect(() => { }); ``` +To force the `$effect` to track the values read _asynchronously_, read them synchronously outside the asynchronous boundary. A clear pattern for this is to evaluate the dependencies using the `void` operator: + +```ts +// @filename: index.ts +declare let canvas: { + width: number; + height: number; + getContext(type: '2d', options?: CanvasRenderingContext2DSettings): CanvasRenderingContext2D; +}; +declare let size: number; + +// ---cut--- +$effect(() => { + const context = canvas.getContext('2d'); + + +++// read `size` synchronously so it is registered as a dependency+++ + +++void size;+++ + + setTimeout(() => { + // ...and now this will also re-run when `size` changes + context.fillRect(0, 0, size, size); + }, 0); +}); +``` + An effect only reruns when the object it reads changes, not when a property inside it changes. (If you want to observe changes _inside_ an object at dev time, you can use [`$inspect`]($inspect).) ```svelte