github solidjs/solid @solidjs/signals@2.0.0-rc.19

Patch Changes

  • 2a0da2f: fix(web): refuse an async memo that resolves to an element (#4242)

    On the server, an async memo (or async iterable source) whose value is rendered output — JSX, a component's result, a <Loading>/<Errored> boundary — can no longer reach the hydration serializer. A boundary's output failed in seroval, so its record never resolved and the hydrating client waited on the memo forever (streamed, the boundary reverted to its fallback); a template object crossed as data, duplicating the markup, and streamed the client could not insert it (UNRECOGNIZED_INSERT_VALUE). The memo now rejects where its value lands, with the new diagnostic ASYNC_MEMO_ELEMENT (recorded on the observe channel; the dev message says what to do instead, other tiers carry the code), and its boundary contains the error like any render error: the nearest <Errored> renders its fallback and the client hydrates it; with none, the <Loading> fragment rejects and the client renders the subtree. Return data from the async memo and render the JSX from it. Ordinary data is never refused (only plain objects in the SSR template shape, boundary outputs and fragment arrays holding them count as rendered output), and content that never hydrates (<NoHydration>) is untouched.

    Also, a fragment failing while a boundary registered under an undefined key (one created inside an async memo's continuation) is pending no longer fails the whole request in renderToStream.

  • 0847122: Two mounts under one boundary, made in separate flushes, keep their holds independent (S-5 rows 1′–3′, D-4, #4283). A boundary's tree and output are structure, not a user derivation of its content: like the render effect that flattens the same children without a boundary, they read a hold they are not part of as committed and are re-derived at its landing, and a re-pass of one outside its hold's flush publishes mainline instead of re-joining that hold and merging it with the next flush's. <Errored> and <Loading> (with or without on) around two <Show>s, each over its own hold, now reveal each branch at its own hold's landing. A re-arm's swap is still the change's work: while it is, the output joins the change (D-13).

  • 0090fe3: Fix: a streamed <Loading> claimed after a client write changed a shell memo it reads now shows the client's value. A hydrated memo adopted while its server answer was still streaming had no snapshot, so once a client write took its compute over, the boundary's claim read the client's value while adopting the server's markup — and kept the server's text (or attribute) for good, since nothing re-ran the binding. The memo's server answer is now its snapshot when it lands, even when the write already superseded it: the claim reads what the markup shows and replays the client's value at its release, as for a written signal. If the client's recompute is still in flight when the boundary resumes, the claim is served the snapshot instead of going pending, so the boundary keeps the server's content (no fallback over it, no detached nodes, no lost queued click) until the client's value lands.

  • 0dd40b2: Release deferred flight bookkeeping when iterator setup or the initial pull throws, so pending verdicts clear and truth readers receive the current error.

  • f811097: A derived store's per-key isPending now reads true while the derive re-asks under a Loading boundary that has shown content, when the verdict is read in the same computation as the value (${state.a} ${isPending(() => state.a)}). The derive's first flight left a commit-beneath-a-flight mark that its setter landing never cleared, so the next re-ask suspended the verdict reader to the boundary (which kept the stale screen with isPending false) instead of serving it the committed value with isPending true, as an async memo does. (#4269)

  • 8b34f22: An error boundary that caught an error rethrown from an inner boundary's fallback now recovers when the inner content does: switching the inner boundary back to its content disposes the fallback that threw, and the outer boundary now drops that error and shows its content again in the same flush, instead of keeping its fallback until some later error re-rendered it (#4270).

  • 2d9c9aa: Drain pending computations after disposal, releasing retired roots while idle.

  • c1009e5: A render effect that reads latest(s) before an async value that has never shown now holds its mount the same way the reverse read order does: an earlier latest() read no longer turns the later unready read into waiting lane work, so a newly mounted branch (for example under <Show>) stays unpublished until the value lands instead of appearing with an empty slot (S-39, D-28) (#4275).

  • 6ca7e8d: latest() of an optimistic value now shows the guess beside the held screen however reads are grouped into render effects (#4284). When a guess's lane is held on a downstream async derivation, a render effect that reads the guess and latest(guess) together no longer parks with the lane and freezes the latest() read: like split effects, it shows 1 x1 / 5 (the committed value and its derivation beside the guess) and 5 x5 / 5 when the derivation lands. The lane still holds the guess until its derivation lands; an unblocked lane costs no extra pass.

  • 3b6f839: A re-armed Loading boundary (on) whose fallback reads pending data through a render effect — any compiled JSX fallback, such as <p>Meta {meta()}</p> — no longer holds the new content's reveal when the content lands first: a fallback the screen never showed is disposed as soon as the content replaces it, so the content shows as soon as it lands and the fallback is never seen, as it already did when the fallback read the data directly (#4268).

  • 4ea2f49: UNSTABLE_MEMO_OUTPUT no longer reports the memo merge() creates for a function source, such as a compiled conditional component spread (<Child {...(on() ? { tone } : {})} />). No one can pass that memo equals, so the warning had no fix (#4256). In observe builds the memo is now named merge. Production output is unchanged.

  • 1d6c40f: Add onFlush(fn): runs fn once right after the pending flush, before paint, without waiting on async work. A write made there is its own update, so it is not held by async work another write in the same frame started (a menu closing while the consumer's onSelect navigates). Meant for event handlers and effect callbacks; dev warns (ON_FLUSH_IN_OWNED_SCOPE) in a component body or computation. Tree-shakes when unused.

  • 1212b77: A derived store (createProjection or createStore(fn)) over a memo now follows the memo after it has shown a value: when the memo goes pending again, isPending on the store's keys reports it, and when the memo later throws, the error reaches the error boundary instead of the store silently keeping its last value. (#4271)

  • e509ad1: A keyed derived store over an async memo follows every landing (#4305): when the source goes pending, the readers of the store's keys wait on the derive itself, so a reader whose value a landing leaves unchanged (a memo per row under mapArray/<For>) is released by the landing instead of holding the first one forever. The same holds when the derive re-runs while its source is pending.

  • 253561e: A memo whose first read rejected stays uninitialized when it is re-asked: an isPending pass and latest() throw NotReady until a value lands instead of reading undefined (#4244).

  • e0e4224: A served answer's arrival during hydration no longer swallows a client write made in the same flush. Writes made before the arrival flush first, outside hydration's own pass, and a write made during it (an effect's, a later round of the flush) makes a node it reaches compute, so the node shows the written value instead of re-serving the server's answer. @solidjs/signals exports its internal flush-round counter (clock) for this.

  • 7f03a4f: A store no longer loses later writes after a batch that writes a nested key and then reconciles its parent. The parent's fold took the full-key path and deleted the $OWNER stamp from the committed clone that the child's path copy had just created, so later draft writes went to a second target: snapshot() saw them, but proxy reads and subscribers stayed on the old value. foldTarget now skips $OWNER in both full-key loops.

  • c225400: IMMUTABLE_UPDATE_IN_STORE no longer reports an array index that now holds an item the array already had, such as the next item shifted down by an in-place splice (the repair the diagnostic recommends). Before, if the removed item and its neighbour shared at least half of their leaves, the move was reported as a spread-copy replacement (#4262). A fresh copy assigned to an index is still reported. Production output is unchanged.

  • 87934c1: Smaller bundles: builds without the optimistic-store, reveal-order or lane hook modules drop their dead branches, and the #4273/#4275 fixes share existing paths.

Don't miss a new solid release

NewReleases is sending notifications on new releases.