From 688820ca1c5eff8be0b1a3bb586626a1c102ce58 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Thu, 22 Oct 2026 15:53:25 +0530 Subject: [PATCH 1/2] docs: async-boundary behaviors and custom-element JSX typing - isPending: an errored expression is not pending; keep a plain read under Errored (async reactivity, isPending reference). - Errored: the fallback stays during a reset() retry; put Loading outside with an `on` the retry changes (boundaries, async reactivity, Errored reference). - action: a rejected action promise from a handler does not reach Errored; catch it in the generator or with .catch() (mutations, action reference). Corrects the sentence that said it did. - TypeScript: declare custom elements by augmenting @solidjs/web's JSX.IntrinsicElements from a module .d.ts; add the missing `export {}` to the web components example. Co-Authored-By: Claude Opus 5.5 --- scripts/extract-solid-ref.mjs | 25 ++++++++++- .../(2)concepts/(3)async-reactivity.mdx | 24 ++++++++++- src/routes/(2)concepts/(4)boundaries.mdx | 40 ++++++++++++++++++ src/routes/(2)concepts/(5)mutations.mdx | 30 ++++++++++++- src/routes/(5)guides/(12)typescript.mdx | 42 +++++++++++++++++++ .../(13)integrate-non-solid-code.mdx | 7 +++- .../(1)solid-js/(1)reactivity/is-pending.mdx | 1 + .../(3)lifecycle-actions/action.mdx | 2 + .../(1)solid-js/(5)components-jsx/errored.mdx | 3 ++ 9 files changed, 168 insertions(+), 6 deletions(-) diff --git a/scripts/extract-solid-ref.mjs b/scripts/extract-solid-ref.mjs index 726147b3e..7ce380dd0 100644 --- a/scripts/extract-solid-ref.mjs +++ b/scripts/extract-solid-ref.mjs @@ -1790,6 +1790,7 @@ const ENTRY_CAVEATS = { isPending: [ "Reports on updates to a value that exists. Before a first value, the read inside `fn` follows the surrounding `Loading` path.", "A bare `refresh()` does not flip `isPending`; pair it with `affects()` to make the re-ask visible.", + "An errored expression is not pending: `isPending` returns `false` and does not rethrow the error. Read the value itself inside an `Errored` boundary to show the failure.", ], latest: [ "Use it on controls, not on content. Data read through `latest` can show a new answer before the rest of the update agrees with it.", @@ -1833,6 +1834,7 @@ const ENTRY_CAVEATS = { "A plain `await` leaves the transaction. Put a bare `yield` before any writes that follow an `await`, or use `yield` in place of `await`.", "Do not call `flush()` inside the generator; it drains the transaction mid-step.", "Navigation-shaped updates (write an input, read the async result) do not need an action. A plain setter call is held automatically.", + "A failure rejects the returned promise and does not reach an `Errored` boundary. Catch it inside the generator, or with `.catch()` where the action is called.", ], affects: [ "Marking does not change the value or trigger a refetch; it only reports pending. Pair with `refresh()` to re-ask.", @@ -1891,6 +1893,7 @@ const ENTRY_CAVEATS = { Errored: [ "The boundary catches errors thrown while rendering its subtree and errors that travel through the reactive graph, including rejected async reads. Handle errors in event handlers where they happen.", "`reset()` recomputes the failing sources. If the cause persists, the boundary errors again.", + "While a `reset()` retry is pending, the fallback stays on screen, so a `Loading` inside the boundary does not show. To show a placeholder during the retry, put `Loading` outside `Errored` with an `on` value that the retry changes.", "Errors thrown from the fallback itself go to the parent `Errored`.", ], Loading: [ @@ -1995,6 +1998,16 @@ const REACTIVITY_PROBLEMS = { "/guides/debugging-reactivity#every-update-stopped-after-an-error", ], }; +const BOUNDARY_PROBLEMS = { + retryKeepsError: [ + "Retry keeps showing the error instead of a loading state", + "/concepts/boundaries#retry-keeps-showing-the-error-instead-of-a-loading-state", + ], + actionNotCaught: [ + "A failed action does not show the `Errored` fallback", + "/concepts/mutations#a-failed-action-does-not-show-the-errored-fallback", + ], +}; const COMPONENT_PROBLEMS = { childNotUpdating: [ "A child does not update when the parent's signal changes", @@ -2083,7 +2096,11 @@ const ENTRY_PROBLEMS = { reconcile: [LIST_PROBLEMS.loseFocus, LIST_PROBLEMS.wholeList], merge: [COMPONENT_PROBLEMS.childNotUpdating], omit: [COMPONENT_PROBLEMS.childNotUpdating], - action: [REACTIVITY_PROBLEMS.deadClick, FORM_PROBLEMS.errorsVanish], + action: [ + REACTIVITY_PROBLEMS.deadClick, + FORM_PROBLEMS.errorsVanish, + BOUNDARY_PROBLEMS.actionNotCaught, + ], affects: [REACTIVITY_PROBLEMS.deadClick], refresh: [REACTIVITY_PROBLEMS.deadClick], children: [COMPONENT_PROBLEMS.childNotUpdating], @@ -2095,7 +2112,11 @@ const ENTRY_PROBLEMS = { LIST_PROBLEMS.wholeList, ], Repeat: [LIST_PROBLEMS.wrongIndex], - Errored: [REACTIVITY_PROBLEMS.stoppedAfterError], + Errored: [ + REACTIVITY_PROBLEMS.stoppedAfterError, + BOUNDARY_PROBLEMS.retryKeepsError, + BOUNDARY_PROBLEMS.actionNotCaught, + ], Loading: [REACTIVITY_PROBLEMS.deadClick], Portal: [COMPONENT_PROBLEMS.refCallback], respond: [FORM_PROBLEMS.respondInternalError, FORM_PROBLEMS.errorsVanish], diff --git a/src/routes/(2)concepts/(3)async-reactivity.mdx b/src/routes/(2)concepts/(3)async-reactivity.mdx index 3d510ea64..968604c6e 100644 --- a/src/routes/(2)concepts/(3)async-reactivity.mdx +++ b/src/routes/(2)concepts/(3)async-reactivity.mdx @@ -313,6 +313,27 @@ With attribution enabled, development builds report that case as `SILENT_HOLD` a If the expression has no settled answer yet, the read inside `isPending` still follows the surrounding `Loading` path; `isPending` reports on updates to a value that exists, not on the first load. +An error is not pending either. +When the request rejects, `isPending(results)` returns `false` and does not rethrow the error, so a region that reads `results` only through `isPending` never reports the failure: + +```tsx +// Avoid: the only read of results is the probe +Search failed.

}> +

Showing results for {query()}

+
+ +// Prefer: the region also reads the value, so a rejection reaches Errored +Search failed.

}> +
    + {(product) =>
  • {product.name}
  • }
    +
+
+``` + +In the `Avoid` version a failed search drops the `stale` class, and the paragraph stays on screen as if the search had succeeded. +In the `Prefer` version the `results()` read inside `` throws the error, and the boundary shows "Search failed." in place of the list. +Keep a plain read of the value next to each `isPending` probe, inside the [`Errored` boundary](#work-rejects-errored) that should report it. + ## Show the input now: `latest` A held update keeps the input's old value visible along with the old content. @@ -464,7 +485,8 @@ A loading boundary does not consume errors, and an error boundary does not repla An error is a status of that part of the graph, not a terminal state. When the data underneath changes, because an input signal changed or a `refresh` landed, the boundary retries and the content returns. `reset` retries the failed sources rather than showing the same error again. -[Boundaries](/concepts/boundaries) covers placement and recovery in detail. +While the retry is in flight, the error fallback stays on screen; the `Loading` inside the boundary does not replace it. +[Boundaries](/concepts/boundaries#recovery) covers placement and recovery in detail, including how to show a placeholder during a retry. ## Read every input before the first `await` diff --git a/src/routes/(2)concepts/(4)boundaries.mdx b/src/routes/(2)concepts/(4)boundaries.mdx index 48a6119e4..6f683ab51 100644 --- a/src/routes/(2)concepts/(4)boundaries.mdx +++ b/src/routes/(2)concepts/(4)boundaries.mdx @@ -135,6 +135,41 @@ The error is the current status of that part of the graph, the same way "not rea `reset` covers the case where nothing upstream will change on its own, such as a network outage. It re-runs the failed sources, so a retry that hits the same error shows the same fallback, and a retry that succeeds shows the content. +While the retry is in flight, the error fallback stays on screen. +In the product panel above, clicking **Retry** leaves the error message and the button in place until the request settles; the `Loading` inside `Errored` does not show "Loading product…", because the error remains the region's answer until a new value replaces it. +When a retry should show the placeholder, move `Loading` outside `Errored` and give it an `on` value that the retry changes: + +```tsx +const [productId, setProductId] = createSignal("mug"); +const [attempt, setAttempt] = createSignal(0); +const product = createMemo(() => fetchProduct(productId())); + +Loading product…

}> + ( +
+

{String(error())}

+ +
+ )} + > +

{product().name}

+

{product().description}

+
+
; +``` + +Click **Retry** and "Loading product…" replaces the error message until the request settles; then the product or a new error message appears. +Picking another product shows the loading text as before. +The `attempt` write re-arms the boundary (see [Show a placeholder again](/concepts/async-reactivity#show-a-placeholder-again-loading-on)); without `on`, the outer `Loading` keeps the error message on screen too, because the region already has content. + :::caution[An error in the fallback is outside the boundary] An error thrown while rendering an `Errored` fallback is not caught by that boundary. A parent `Errored` can catch it. @@ -267,6 +302,11 @@ Give the sibling its own `on`, or drop `on` and show the wait with `isPending`. If the cause has not changed, the same error comes back. When the fix is a different input, change the input; the boundary recovers on its own. +### Retry keeps showing the error instead of a loading state + +An `Errored` boundary keeps its fallback while a `reset` retry is pending, so a `Loading` inside it cannot show its fallback for the retry. +Put `Loading` outside `Errored` and add a value that the **Retry** button changes to its `on`, as in [Recovery](#recovery). + ### A region inside `Reveal` does not appear when its data is ready The region is a slot in a `sequential` group and an earlier slot is not ready. diff --git a/src/routes/(2)concepts/(5)mutations.mdx b/src/routes/(2)concepts/(5)mutations.mdx index 8269b9fdd..c8ecc5905 100644 --- a/src/routes/(2)concepts/(5)mutations.mdx +++ b/src/routes/(2)concepts/(5)mutations.mdx @@ -321,7 +321,8 @@ The projection reads the durable items and folds in the `errors` map. The map is not reactive by itself; `refresh(items)` re-runs the projection after each mutation and makes its current entries visible. The action catches the expected failure because the row can recover locally. -An error the action does not catch, or an error thrown by the projection or the render, still reaches `Errored`. +An error thrown by the projection or the render still reaches `Errored`. +An error the action does not catch rejects the promise that `setGiftWrap` returns, and the `void` in the handler discards that promise, so no boundary sees the failure; see [A failed action does not show the Errored fallback](#a-failed-action-does-not-show-the-errored-fallback). ## Common problems @@ -341,6 +342,31 @@ Make the tentative write inside the `action` that sends the request, and suspend The store is a plain `createStore`, whose writes are durable even inside an action. Use `createOptimisticStore` for state that should revert when the request settles. +### A failed action does not show the `Errored` fallback + +An action call returns a promise, and an event handler does not wait for it. +When the action throws, the promise rejects outside the reactive graph: the optimistic overlay is dropped, the browser reports an unhandled rejection, and no `Errored` boundary shows its fallback. +`Errored` catches errors from reads and rendering, not from promises that a handler starts. + +Handle the failure where the action is called. +Catch it inside the generator when the action can recover, as `setGiftWrap` does above, or attach `.catch()` to the call and write the error to a signal the UI reads: + +```tsx +const [saveError, setSaveError] = createSignal(); + +; +{(message) =>

{message()}

}
; +``` + +Click **Save** while the request fails and the message appears under the button; clicking again clears it before the next attempt. + ### `flush()` inside an action throws `[FLUSH_IN_ACTION]` An action's writes are held in its transaction until the action settles, so there is nothing for `flush()` to show. @@ -362,6 +388,6 @@ Remove the call and assert after the action's promise resolves. - [Forms](/guides/forms): the same actions behind a `
`, with progressive enhancement, validation, and errors returned from the server. - [Mutations and responses](/building-apps/server-functions/mutations-and-responses): writing `api.add` as a `"use server"` function and returning redirects, statuses, and safe errors. -- [Boundaries](/concepts/boundaries): where `Errored` catches the errors an action does not, and how an errored region recovers. +- [Boundaries](/concepts/boundaries): where `Errored` catches errors from reads and rendering, and how an errored region recovers. - [Data loading and mutations](/routing/solid-router/data): with Solid Router mounted, the same actions call server functions and the router applies their revalidation and redirects; `query` adds caching and `formAction` adds forms. - [State management](/guides/state-management): where the cart, the user, and the filter live, and why `createCart()` runs inside a provider. diff --git a/src/routes/(5)guides/(12)typescript.mdx b/src/routes/(5)guides/(12)typescript.mdx index c57f4c900..7c426d95c 100644 --- a/src/routes/(5)guides/(12)typescript.mdx +++ b/src/routes/(5)guides/(12)typescript.mdx @@ -279,6 +279,43 @@ The `!` says the variable is assigned before it is read, which holds when every A component that exposes its element takes a `Ref` prop, `T | ((val: T) => void) | undefined | Ref[]` from `solid-js`, and passes it straight through to `ref`. The [`ref` reference](/reference/solid-web/jsx-properties/ref) lists what the attribute accepts. +## Custom elements + +The checkout form renders a `` custom element, and the checker reports `Property 'date-picker' does not exist on type 'JSX.IntrinsicElements'` until the tag is declared. +Declare it once by augmenting the `JSX` namespace of `@solidjs/web`, the package `jsxImportSource` points at: + +```ts +// src/types/custom-elements.d.ts +export {}; + +declare module "@solidjs/web" { + namespace JSX { + interface IntrinsicElements { + "date-picker": HTMLAttributes & { + value?: string; + "prop:disabledDates"?: string[]; + onDatechange?: (event: CustomEvent<{ value: string }>) => void; + }; + } + } +} +``` + +With the file in place, `` type-checks, `event.detail.value` in `onDatechange` is a `string`, and `prop:disabledDates={1}` reports `Type 'number' is not assignable to type 'string[]'`. +Inside the augmented namespace, `HTMLAttributes` is the namespace's own interface, which supplies the global attributes, `ref`, and the standard event handlers. + +Two details decide whether the declaration applies: + +- The file must be a module. + The `export {}` line, or any `import` or `export`, makes `declare module` an augmentation that merges into the package's types. + Without it the file is a global script, and the same block declares a new `@solidjs/web` module that replaces the package's types: imports such as `render` report `Module '"@solidjs/web"' has no exported member 'render'`, and the tag is still unknown. +- No `.ts` file next to it may share its name. + TypeScript treats `date-picker.d.ts` beside `date-picker.ts` as that file's compiled output and leaves it out of the program, so the declaration never applies and nothing reports why. + +Augment `@solidjs/web`, not `solid-js`. +`solid-js` has no `JSX` namespace, so a `declare module "solid-js"` block does not change what the checker accepts in JSX. +[Web components](/guides/integrate-non-solid-code#web-components) covers how attributes, `prop:` properties, and events reach the element at runtime. + ## Server functions and the router A `"use server"` function is typed like any async function, and the caller sees its declared return: @@ -378,6 +415,11 @@ A function is not a valid child, so the checker reports it; add the parentheses. The `ref` variable is typed `HTMLInputElement | undefined` and read in the component body, before the element exists. Move the read into an event handler or `onSettled`, and declare the variable with `!` once every read happens after creation. +### `Module '"@solidjs/web"' has no exported member` after adding a declaration file + +The `.d.ts` file that declares a custom element has no `import` or `export`, so its `declare module "@solidjs/web"` block replaces the package's types instead of adding to them. +Add `export {};` at the top of the file; see [Custom elements](#custom-elements). + ## Recap - Type `props` on the parameter, or use `Component

` when the component is a value; both produce the same checks. diff --git a/src/routes/(5)guides/(13)integrate-non-solid-code.mdx b/src/routes/(5)guides/(13)integrate-non-solid-code.mdx index c105f274a..fe19b914e 100644 --- a/src/routes/(5)guides/(13)integrate-non-solid-code.mdx +++ b/src/routes/(5)guides/(13)integrate-non-solid-code.mdx @@ -131,10 +131,12 @@ Declare it once, with the attribute, property, and event names the element suppo ```ts // src/types/date-picker.d.ts +export {}; + declare module "@solidjs/web" { namespace JSX { interface IntrinsicElements { - "date-picker": JSX.HTMLAttributes & { + "date-picker": HTMLAttributes & { value?: string; "prop:disabledDates"?: string[]; onDatechange?: (event: CustomEvent<{ value: string }>) => void; @@ -144,6 +146,9 @@ declare module "@solidjs/web" { } ``` +The `export {}` line keeps the file a module, so the block adds to the types of `@solidjs/web` instead of replacing them. +[Custom elements](/guides/typescript#custom-elements) in the TypeScript guide covers the rules for this file. + ## Render Solid into a foreign container Two situations move Solid content into a DOM node Solid did not create. diff --git a/src/routes/reference/(1)solid-js/(1)reactivity/is-pending.mdx b/src/routes/reference/(1)solid-js/(1)reactivity/is-pending.mdx index 7f4378685..b639af1c8 100644 --- a/src/routes/reference/(1)solid-js/(1)reactivity/is-pending.mdx +++ b/src/routes/reference/(1)solid-js/(1)reactivity/is-pending.mdx @@ -71,6 +71,7 @@ const results = createMemo(() => searchProducts(query())); - Reports on updates to a value that exists. Before a first value, the read inside `fn` follows the surrounding `Loading` path. - A bare `refresh()` does not flip `isPending`; pair it with `affects()` to make the re-ask visible. +- An errored expression is not pending: `isPending` returns `false` and does not rethrow the error. Read the value itself inside an `Errored` boundary to show the failure. ## Common problems diff --git a/src/routes/reference/(1)solid-js/(3)lifecycle-actions/action.mdx b/src/routes/reference/(1)solid-js/(3)lifecycle-actions/action.mdx index 37914d308..8f841739e 100644 --- a/src/routes/reference/(1)solid-js/(3)lifecycle-actions/action.mdx +++ b/src/routes/reference/(1)solid-js/(3)lifecycle-actions/action.mdx @@ -120,11 +120,13 @@ await addTodo("buy milk"); - A plain `await` leaves the transaction. Put a bare `yield` before any writes that follow an `await`, or use `yield` in place of `await`. - Do not call `flush()` inside the generator; it drains the transaction mid-step. - Navigation-shaped updates (write an input, read the async result) do not need an action. A plain setter call is held automatically. +- A failure rejects the returned promise and does not reach an `Errored` boundary. Catch it inside the generator, or with `.catch()` where the action is called. ## Common problems - [The screen looks dead after a click](/guides/debugging-reactivity#the-screen-looks-dead-after-a-click) - [Errors show for a moment and then vanish](/guides/forms#errors-show-for-a-moment-and-then-vanish) +- [A failed action does not show the `Errored` fallback](/concepts/mutations#a-failed-action-does-not-show-the-errored-fallback) ## Learn more diff --git a/src/routes/reference/(1)solid-js/(5)components-jsx/errored.mdx b/src/routes/reference/(1)solid-js/(5)components-jsx/errored.mdx index f4edad008..f9c6c6509 100644 --- a/src/routes/reference/(1)solid-js/(5)components-jsx/errored.mdx +++ b/src/routes/reference/(1)solid-js/(5)components-jsx/errored.mdx @@ -71,11 +71,14 @@ The subtree to guard. - The boundary catches errors thrown while rendering its subtree and errors that travel through the reactive graph, including rejected async reads. Handle errors in event handlers where they happen. - `reset()` recomputes the failing sources. If the cause persists, the boundary errors again. +- While a `reset()` retry is pending, the fallback stays on screen, so a `Loading` inside the boundary does not show. To show a placeholder during the retry, put `Loading` outside `Errored` with an `on` value that the retry changes. - Errors thrown from the fallback itself go to the parent `Errored`. ## Common problems - [Every update stopped after an error](/guides/debugging-reactivity#every-update-stopped-after-an-error) +- [Retry keeps showing the error instead of a loading state](/concepts/boundaries#retry-keeps-showing-the-error-instead-of-a-loading-state) +- [A failed action does not show the `Errored` fallback](/concepts/mutations#a-failed-action-does-not-show-the-errored-fallback) ## Learn more From 43fd782d9b27d586b888165044de865dd0b70a14 Mon Sep 17 00:00:00 2001 From: Dev Agrawal Date: Thu, 22 Oct 2026 16:06:28 +0530 Subject: [PATCH 2/2] docs(ref): dispositions for from, FromSource, broadcast, setStatefulProperty The reference generator stopped on four exports new on `next`: - from: a reference page under Advanced / Interop & Async, from the @solidjs/signals JSDoc (PREFERRED_SOURCE_PATHS), with a parameter doc and the hydration caveat from the solid-js wrapper. - FromSource: folded into the from page. - broadcast: hidden; @experimental, and its shared render is the frames server half the reference excludes. - setStatefulProperty: hidden with the compiler/DOM-runtime ABI; the client export is @internal, but the server entry re-exports a stub under the same name without JSDoc. The generator now completes against next (aece1d448). Pages that changed only through unrelated upstream drift are left out of this commit. Co-Authored-By: Claude Opus 5.5 --- scripts/extract-solid-ref.mjs | 19 ++++ .../(6)advanced/(6)interop-async/from.mdx | 96 +++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 src/routes/reference/(1)solid-js/(6)advanced/(6)interop-async/from.mdx diff --git a/scripts/extract-solid-ref.mjs b/scripts/extract-solid-ref.mjs index 7ce380dd0..8a774247c 100644 --- a/scripts/extract-solid-ref.mjs +++ b/scripts/extract-solid-ref.mjs @@ -312,6 +312,7 @@ const ADVANCED_ROUTES = { "Advanced / Interop & Async", ], flatten: ["advanced/interop-async/flatten.mdx", "Advanced / Interop & Async"], + from: ["advanced/interop-async/from.mdx", "Advanced / Interop & Async"], NotReadyError: [ "advanced/interop-async/not-ready-error.mdx", "Advanced / Interop & Async", @@ -563,6 +564,7 @@ const FOLD_INTO = { EffectFunction: "createEffect", EffectOptions: "createEffect", ExternalSource: "enableExternalSource", + FromSource: "from", ExternalSourceConfig: "enableExternalSource", ExternalSourceFactory: "enableExternalSource", HydrationProjectionOptions: "createProjection", @@ -632,6 +634,10 @@ const FOLD_INTO = { }; const HIDDEN_EXPORTS = new Set([ + // `@experimental`: the shared render behind it is the frames server half, + // which the reference excludes with the rest of frames. On the client it + // is exactly `live(fn)`. + "broadcast", // Compiler slot protocol; not part of the application-facing reference. "SLOT_VALUE", "SLOT_MARKER", @@ -725,6 +731,9 @@ const HIDDEN_EXPORTS = new Set([ "setAttribute", "setAttributeNS", "setProperty", + // Dev-compiled output's stateful property write (`@internal` in client.ts; + // the server entry re-exports a stub under the same name without JSDoc). + "setStatefulProperty", "setStyleProperty", "style", "unregisterDelegatedContainer", @@ -1078,6 +1087,9 @@ const PREFERRED_SOURCE_PATHS = { ServerErrorContext: "packages/web/src/server.ts", ServerErrorHook: "packages/web/src/server.ts", ServerErrorsConfig: "packages/web/src/server.ts", + // The engine's JSDoc carries the contract; solid-js only adds the + // hydration brand, which the caveats state. + from: "packages/signals/src/from.ts", }; const ENTRY_EXAMPLES = { @@ -1443,6 +1455,10 @@ async function exportReport() { // Shape: { symbol: { paramName: "text" | { type?, text? } } }. An object // can pin the displayed type when the overload union is noisy. const PARAM_DOCS = { + from: { + source: + "A subscribe function `(next, error, complete) => unsubscribe`, an object with `subscribe(observer)` such as an Observable, or an `AsyncIterable`. See `FromSource` below.", + }, createSignal: { value: "Initial value. Pass a function through the `fn` form instead; a function here is rejected by the types.", @@ -1796,6 +1812,9 @@ const ENTRY_CAVEATS = { "Use it on controls, not on content. Data read through `latest` can show a new answer before the rest of the update agrees with it.", "Before any value exists, the read still suspends like a normal read.", ], + from: [ + "During hydration, a subscription read on the server reconnects on the client once its section has hydrated, instead of staying at the server's value.", + ], untrack: [ "Untracking a read does not stop the computation from re-running for its other tracked reads.", "Untracking does not opt out of async settlement. An initial `NotReadyError` still suspends the owning computation and retries it when the source first settles.", diff --git a/src/routes/reference/(1)solid-js/(6)advanced/(6)interop-async/from.mdx b/src/routes/reference/(1)solid-js/(6)advanced/(6)interop-async/from.mdx new file mode 100644 index 000000000..9ebf7dc04 --- /dev/null +++ b/src/routes/reference/(1)solid-js/(6)advanced/(6)interop-async/from.mdx @@ -0,0 +1,96 @@ +--- +title: "from" +category: "Advanced / Interop & Async" +use_cases: "advanced interop & async api, from usage" +tags: + - "from" + - "advanced" + - "interop" + - "async" + - "reference" + - "api" + - "v2" +version: "2.0" +description: "Converts a callback subscription, an Observable or an AsyncIterable into an AsyncIterable for a Solid computation." +source_repo: "solidjs/solid" +source_ref: "next" +source_path: "packages/signals/src/from.ts" +--- + +{/* Generated by scripts/extract-solid-ref.mjs. Edit the source JSDoc or disposition map, then regenerate. */} + +Converts a callback subscription, an Observable or an AsyncIterable into an +AsyncIterable for a Solid computation. A subscription keeps only the latest +value, and a value the source emits during `subscribe` is served without a +loading frame; an AsyncIterable streams every value through. + +The source closes on supersede — the owning computation re-running — and +on owner disposal. Inside an async generator memo that holds for a +`for await` that starts before the first `await`; one that starts after it +has no owner to bind to, so its source closes only when the loop ends. +Prefer returning `from(...)` from its own memo, with awaited inputs read +from upstream memos and transforms done in a downstream memo. + +Iterate the result with `for await` or return it from a computation: its +`next()` returns an already-buffered value as a bare result, not a promise. + +## Import + +```ts +import { from } from "solid-js"; +``` + +## Type signature + +```ts +function from(source: FromSource): AsyncIterable; +``` + +## Parameters + +### `source` + +- **Type:** `FromSource` + +A subscribe function `(next, error, complete) => unsubscribe`, an object with `subscribe(observer)` such as an Observable, or an `AsyncIterable`. See `FromSource` below. + +## Examples + +```ts +const position = createMemo(() => + from((next, error) => { + const id = navigator.geolocation.watchPosition(next, error); + return () => navigator.geolocation.clearWatch(id); + }) +); +const ticks = createMemo(() => from(interval$(rate()))); +``` + +## Caveats + +- During hydration, a subscription read on the server reconnects on the client once its section has hydrated, instead of staying at the server's value. + +## Learn more + +- [Async reactivity](/concepts/async-reactivity) +- [Integrate non-Solid code](/guides/integrate-non-solid-code) + +## Related types + +### `FromSource` + +Anything `from` accepts: a subscribe function +(`(next, error, complete) => unsubscribe`), an object with +`subscribe(observer)` (RxJS and TC39 Observables, including +`Symbol.observable` / `"@@observable"` interop), or an AsyncIterable. + +```ts +type FromSource = + | (( + next: (value: T) => void, + error: (err: unknown) => void, + complete: () => void + ) => Teardown) + | { subscribe(observer: FromObserver): Teardown } + | AsyncIterable; +```