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

pre-release3 hours ago

Patch Changes

  • 4f8daa2: A derived store row first read during a held adoption is paired with its old row the way the adoption pairs rows (by the family's key), so reading a field the adoption left unchanged (cards.find(...)?.id) no longer holds an independent write for the action's lifetime (#3712). A changed field still holds.

  • ad973ed: Async holds follow the reader, and are shorter:

    • A first load read by a render effect outside a <Loading> makes the change that mounted it wait (the async answer of a memo created under a hold lands with that hold); a <Loading> that has not shown content catches it instead and shows its fallback.
    • Creating a memo no longer pulls the whole tick into a hold: the render effect that reads it decides. A memo nobody renders holds nothing.
    • isPending or latest alone never holds a change; a plain read beside them still does.
    • isPending of a memo born held joins the hold with its reader instead of reporting a verdict.
    • An optimistic store row's server echo is judged as reconcile would (key order free, a self-referencing row is a correction) instead of by JSON.stringify.
    • An errored reader is not re-run when a transaction lands, so an errored derived store is not refetched by a row that also reads isPending (#3945, #3962).
    • refresh()'s promise reads the truth beneath a guess the same way until() does (#3895).
    • An optimistic signal (createOptimistic) judges its source's answer with its own equals, as signals do: a server's new array or object is a correction unless equals says it is the same. (next compared every optimistic answer with JSON.stringify.) Store rows follow reconcile: equal Date objects, or { t: undefined } against a missing key, are corrections.
  • b2bec73: A render effect that reads a memo born into a held change and, in the same pass, a fresh isPending no longer re-runs forever while the change is held (#3969). Under the dev server every component runs inside such a memo (hot reload), so a refetch that swapped a <Show> to a fallback containing <Spinner on={isPending(x)} /> tripped the "Potential Infinite Loop Detected" guard. A read of a node born held no longer counts as a staged read: there is no committed value to re-derive on.

  • 49a765c: A boundary's fallback is instantiated once per show. An <Errored> whose content reads a failing async memo through a <Show> rendered its fallback twice for one failure and rebuilt it when a reset() retry failed again, losing focus on the Retry button (#3991). The fallback is now built in a scope of its own beside the boundary's output and kept while it shows; a later error reaches it through the error accessor. The same holds for a <Loading> fallback across the boundary's re-derives.

  • 0c70907: The errored-store refetch guard lives on the store pull, so a bundle that never reads a store does not carry it

  • 5a26ebe: Fix an awaited refresh() never settling in production builds: the waiter is always created under a root, so it re-runs when the refetch lands instead of being released as an unobserved node. yield refresh(x) in an action now finishes, and the refetched value reaches the UI.

  • c189013: Derived-store landings finalize their attribution flight

  • a963ec1: Fix a derived store (createStore(fn)) whose re-fetch rejects never reaching Errored: a render effect outside the flight's flush kept showing the last value instead of throwing the derive's error, for sync-to-async and async-to-async derives alike. Memos were unaffected.

  • 0c70907: A row that reads an errored store field and isPending does not refetch the source

  • ad973ed: A createSignal(fn) or createMemo(fn) derivation that reads latest() or isPending() of a signal an in-flight async memo is holding receives its previous value as prev on every re-run under the hold. When its first pass under the hold was equal to the previous value, the next pass received an empty internal marker instead, so (prev = []) => [...prev] threw prev is not iterable and halted the reactive system.

  • 39051bf: Tracked store reads keep a data value that replaced an own getter

  • ef1c297: Fix affects(store) / affects(store, key) throwing TypeError: _.O is not a function in tree-shaken production and observe bundles (#3891). The store half of affects() was installed by a side-effect-only import that bundlers drop under "sideEffects": false; affects() now installs it on its first store-targeted call, and the store half reaches the store engine through hook slots instead of importing it — so a store app that never calls affects() carries none of it, and an app without stores never pulls in the store engine. Thanks to @nickshiro, whose #3912 proposed installing it from affects().

  • ade4c46: Attribution: GRAPH_GROWTH no longer counts the previous route's computations when a navigation settles through a held transition (#3989). The navigation record still settles in the landing, but its graph walk waits for that drain's end, after the landing's commits dispose the page left — the same instant a one-drain navigation is counted. Observe tier only; no production bytes.

  • eae6e03: GRAPH_GROWTH no longer reports a route whose correctly owned content is bigger on each visit (/rows/1, /rows/3, /rows/5) when leaving it disposes everything. While the check is on, each non-initial navigation also counts what outlived it — the nodes alive when it opened that are still alive at its settle — and a series reports only when it climbs both in the totals, as before, and in that count. A navigation that stays on the route pattern (only params or query change) continues the visit and is not compared. The finding's data gains survived beside history, and its message shows both (owners 3 → 5 → 7 (of 6 → 10 → 14)). Fixes #4007.

  • c9027a9: A branch a held write mounts reads the value that mounted it on every pass. A binding in the new branch of a non-keyed <Show> that ran again before the landing — because it probes isPending on data the same write started loading, or because a live write reached it — was served the committed screen, where the condition is still null, and threw into <Errored> (where an unchanged landing then left the error up). Members of the frame a held pass built are now flagged at the park seam and read their transaction's world whichever flush runs them; a verdict reader among them waits on the transaction's flight with its frame instead of being served the committed value (#4010).

  • 20db6a2: A Loading boundary armed by latest() leaves its fallback once the shared source settles.

  • 1c14992: An async-generator action's until resolves when an external source echoes the optimistic row after await, instead of timing out on an acknowledgement the source already contains.

  • d8ba741: An effect reading every row of an optimistic store no longer spins flush forever when the source's answer moves the optimistically written row to an earlier index (#4014). The lane seam re-enqueued the leaked effect as a mainline re-derivation, so its first held read joined the parked transaction and was served the staging, and its later read of the written value re-entered the lane and leaked again, every round. A leaked effect is now re-seated in its lane, as a computed already is by its override, and reads the held slots as committed.

  • e9d549f: A createProjection whose re-run throws synchronously (for example, reading an entry that has since failed) now delivers the error to the readers of its fields, so the nearest <Errored> shows it instead of the screen keeping the last good value.

  • c689a9a: Awaited refresh of an optimistic accessor returns the source answer, not the caller's override.

  • a2dff39: A structural store write during hydration's snapshot capture (a row removed from an array from onSettled, by splice or reconcile) records the container's pre-write backing as its snapshot, the way a leaf write already does. Readers inside the snapshot scope, such as a hydrated <For>, are held on the server's list until the scope is released instead of re-running mid-claim against a snapshot length and live items, which rendered a row for undefined, threw, and halted reactivity (#3950). A record with more than 32 keys commits such a write to a copy of its backing, so the recorded snapshot keeps the pre-write key set for a scoped reader created after the flush.

  • f6905dc: isPending and latest on a superseded write do not depend on reads inside the parked computation.

  • e0a7548: until() through a derivation of an optimistic value resolves once the source confirms the guess (#3943). A derivation's optimistic value counts as the truth when every guess it depends on has been confirmed (its source produced a value equal to the guess) and none of its other inputs has a write still held by the action; nothing is recomputed. A guess made again after a confirmation, an unconfirmed guess the derivation reads, or a held write it reads keeps until() waiting, as before.

  • a24e911: An untracked isPending or latest inside a computation is a snapshot: it no longer makes the computation re-run when one of its own dependencies goes pending. A router link claim that probes pending state while its anchor is created no longer rebuilds the <Show> around it on every refetch of its condition (#3946). Event handlers are unchanged, and a throw from an unresolved source still reaches the computation through untrack.

Don't miss a new solid release

NewReleases is sending notifications on new releases.