From f432ec808b869c0464744607f00f4f00e3f4f853 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Nov 2026 22:07:02 +0000 Subject: [PATCH] docs: when to use createDeferred instead of latest or createMemo Adds a "Let a panel lag" section to the async reactivity concept page, a row for it in the update decision table, and caveats on the reference page contrasting it with reading every consumer through latest (solidjs/solid#4773). Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_0117BoWNSSgtemsSYsaX2hXE --- scripts/extract-solid-ref.mjs | 5 ++ .../(2)concepts/(3)async-reactivity.mdx | 52 +++++++++++++++++-- .../(1)reactivity/create-deferred.mdx | 6 +++ 3 files changed, 58 insertions(+), 5 deletions(-) diff --git a/scripts/extract-solid-ref.mjs b/scripts/extract-solid-ref.mjs index 7b35b1473..cbea4d00f 100644 --- a/scripts/extract-solid-ref.mjs +++ b/scripts/extract-solid-ref.mjs @@ -104,6 +104,11 @@ const CANONICAL_ROUTES = { createEffect: ["reactivity/create-effect.mdx", "Reactivity"], createDeferred: ["reactivity/create-deferred.mdx", "Reactivity"], createMemo: ["reactivity/create-memo.mdx", "Reactivity"], + createDeferred: [ + "`createDeferred` is not `latest` on every consumer. `latest` shows a value now while the update stays held for every other reader; a deferred memo is not part of the update, so nothing waits for it. See [Let a panel lag](/concepts/async-reactivity#let-a-panel-lag-createdeferred).", + "Use it for data that may fall behind the rest of the page without making it wrong, such as an independent widget or a feed. Data that must agree with the rest of the page belongs in `createMemo`.", + "Its first load still suspends to the nearest `Loading` boundary, unless `loadingValue` is set.", + ], createOptimistic: ["reactivity/create-optimistic.mdx", "Reactivity"], createSignal: ["reactivity/create-signal.mdx", "Reactivity"], flush: ["reactivity/flush.mdx", "Reactivity"], diff --git a/src/routes/(2)concepts/(3)async-reactivity.mdx b/src/routes/(2)concepts/(3)async-reactivity.mdx index 52941439f..3455dd8b8 100644 --- a/src/routes/(2)concepts/(3)async-reactivity.mdx +++ b/src/routes/(2)concepts/(3)async-reactivity.mdx @@ -241,15 +241,17 @@ The question to ask is about the update, not about any one request, and it is on **While this update is in flight, what should the user see?** -| The user should see… | Use | -| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | -| Nothing change until everything that depends on the input is ready | The default. Add [`isPending`](#another-answer-is-coming-ispending) to acknowledge the click. | -| The control they touched reflect their input now, and the content wait | [`latest`](#show-the-input-now-latest) on the control, `isPending` to dim the content. | -| A placeholder in place of the content for the changed subject | [``](#show-a-placeholder-again-loading-on) around that content. | +| The user should see… | Use | +| ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| Nothing change until everything that depends on the input is ready | The default. Add [`isPending`](#another-answer-is-coming-ispending) to acknowledge the click. | +| The control they touched reflect their input now, and the content wait | [`latest`](#show-the-input-now-latest) on the control, `isPending` to dim the content. | +| A placeholder in place of the content for the changed subject | [``](#show-a-placeholder-again-loading-on) around that content. | +| The input and the rest of the page change now, and one panel keep its old content until its own answer lands | [`createDeferred`](#let-a-panel-lag-createdeferred) for that panel's data, `isPending` to dim it. | The first two rows are the same screen: the tab highlight moves at once, the content stays and dims, the content swaps when it is ready. That is the standard interaction for a held update, and the rest of this section shows the pieces. The third row is for content that should not stay on screen when its subject changes, such as a panel for a different account. +The fourth row is for a panel that may fall behind the rest of the page without making it wrong, such as the ten-second audit log. The hold applies only to updates. Each panel's first load still reaches the nearest `Loading` boundary, and nested boundaries let panels appear as their own first data arrives. @@ -389,6 +391,46 @@ This is the everyday shape of a held update, not a special case. Reading data through `latest` shows a result before the rest of the update has agreed on it, so a panel can show the new period's numbers while the selector still shows the old period. When you want the control to lead, use `latest` on the control; when you want a panel to show a placeholder, use the next section. +## Let a panel lag: `createDeferred` + +`latest` changes what one reader shows. +The update is still held: every plain reader of the input waits, and the page commits when the slowest answer lands. +When a panel may keep its previous answer while the next one loads, and the rest of the page should not wait for it, create the panel's data with [`createDeferred`](/reference/solid-js/reactivity/create-deferred) instead of `createMemo`: + +```tsx +function AuditPanel(props: { period: string }) { + const entries = createDeferred(() => fetchAudit(props.period)); + + return ( +
    entries()) }}> + {(entry) =>
  • {entry.text}
  • }
    +
+ ); +} +``` + +After its first answer, a refetch of a deferred memo holds nothing. +When the period changes, the selector and the other two panels change at once. +The audit panel keeps its old entries, dims through `isPending`, and shows the new entries when its own request lands. + +The two APIs look alike but answer different questions: + +- `latest` is on the reader. This control shows the input now; the content still waits with the update. +- `createDeferred` is on the source. No reader waits for this answer, so the update does not wait for it either. + +So `createDeferred` is not the same as reading every consumer through `latest`. +With `latest`, the update is still held for every plain read of the input until the slow answer lands. +With `createDeferred`, the slow answer is no longer part of the update at all. + +Reach for `createDeferred` when the async work can lag without making the page wrong: an independent widget, a feed, or search suggestions. +Keep `createMemo` for data that must agree with the rest of the page, such as totals that must add up or the details of the selected item. + +:::note +A deferred memo may lag, but it never leads. +Its first load is a first load like any other: readers suspend to the nearest `Loading` boundary. +A refetch that reads a value another update is still holding lands with that update, so the panel never shows an answer the rest of the page has not committed to. +::: + ## Show a placeholder again: `Loading on` Once a `Loading` boundary has shown content, a later update does not bring the fallback back; the content stays and the update is held. diff --git a/src/routes/reference/(1)solid-js/(1)reactivity/create-deferred.mdx b/src/routes/reference/(1)solid-js/(1)reactivity/create-deferred.mdx index ec8129c3e..671682fac 100644 --- a/src/routes/reference/(1)solid-js/(1)reactivity/create-deferred.mdx +++ b/src/routes/reference/(1)solid-js/(1)reactivity/create-deferred.mdx @@ -64,6 +64,12 @@ const rows = createDeferred(() => fetchRows(store.period)); ; ``` +## Caveats + +- `createDeferred` is not `latest` on every consumer. `latest` shows a value now while the update stays held for every other reader; a deferred memo is not part of the update, so nothing waits for it. See [Let a panel lag](/concepts/async-reactivity#let-a-panel-lag-createdeferred). +- Use it for data that may fall behind the rest of the page without making it wrong, such as an independent widget or a feed. Data that must agree with the rest of the page belongs in `createMemo`. +- Its first load still suspends to the nearest `Loading` boundary, unless `loadingValue` is set. + ## Learn more - [Reactivity](/concepts/reactivity)