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

pre-release7 hours ago

Patch Changes

  • db8241c: A cleanup (onCleanup or an effect's returned cleanup) that throws no longer aborts the rest of the teardown: every other cleanup in the tree still runs, and the first error is rethrown once the teardown is done. Outside a flush (a root's dispose()), the dispose call rethrows it. Inside a flush (a branch switch retiring a frame, a pass replacing an uncommitted one, an effect disposing a root), flush() rethrows it after the flush has finished, so commits and effects are never left half-applied, and lazy nodes released before the throw are still released. An unobserved hook that throws is handled the same way. If an uncaught error halts the reactive system, a collected cleanup error is dropped rather than surfacing later from an unrelated dispose(). On the server, disposeOwner runs every cleanup the same way, so renderToStream's final dispose still reports the error (#4193).

  • 690228a: Clearer dev message for ContextNotFoundError (#4181). undefined still means "not provided" for context, but the message now names the context and says which case it hit: no provider and no default; a createContext(undefined) default, which is no default; or a provider that passed undefined, which counts as not provided. Each suggests null as the explicit empty value (createContext<T | null>(null)). Applies on the client and the server. Production messages and bundles are unchanged. The context docs and cheatsheet now say that undefined is "not provided" and that null is how to make a context optional.

  • 3373ae1: Retain successful falsy fallbacks in mapArray and repeat when their input remains empty, while preserving retries after suspended fallback creation.

  • 411f73b: The dev REACTIVITY_HALTED message now says where the uncaught error escaped: it ends with the owner path of the computation that threw (in <App> › <Composer> › effect, plus the at source line when there is one), and the "Update ignored" notice a later write logs repeats it (#4178). The structured event carries the same ownerPath. A computed that throws into the effect reading it is named, not the effect. Behaviour is unchanged (the system still halts), and production bundles do not change.

  • ee4963a: A keyed optimistic store's arrangement guess is no longer voided when the source answers with something different while the guess's action is open: the answer folds beneath it until the hold commits, and rows then match by key. Two overlapping actions on one list used to leave the store torn after the first one's echo (a hole at its index, length from the guess, a raw target served for a row), and the confirmed row remounted twice at the commit, taking an open editor and its draft with it. Now the screen keeps both guesses whole, nothing remounts, and a row another tab added shows when the write settles. Memos and effects outside the optimistic lane still derive from the answer right away and are held with it, and latest() shows it.

  • 2c442c4: A projection whose re-run throws in a flush (a signal it reads written just before its promise rejects) reaches the error boundary again (#4189).

  • b2d9ed0: Restore deferred lazy releases when an unfinished frame's cleanup throws, so later unrelated lazy memos still clean up when their last observer is removed.

  • f7f30b9: Dispose superseded createReaction arms even when their initial tracking read suspends or throws.

  • a99550e: Dev: writing to a store proxy outside its setter (store.n = 5, store.items.push(x), delete store.user, Object.defineProperty(store, …)) is still ignored, but no longer silently — a new STORE_WRITE_OUTSIDE_SETTER warning reports it once per store object and key, with the setStore(s => { s.key = value }) form of the write (#4179). No production bytes.

  • 6cca8b5: Dev builds now warn with UNCOMMITTED_INPUT_OVERWRITTEN (#4186) when a stateful DOM property binding (value/checked on an input, value on a textarea, value/selected on an option, muted on media) is about to re-write the value it wrote last time over an edit the user never committed to the bound signal — e.g. value={props.name} onChange={commit} with no draft signal, when another dynamic attribute in the same template changes. Runtime behavior is unchanged: the write still happens, once per element and property the warning names the fix (write the signal from onInput, use defaultValue/defaultChecked for an uncontrolled element, or write a rejected edit back with e.currentTarget.value = next).

    Production output is unchanged byte for byte. In dev DOM output both compilers write stateful properties through a new setStatefulProperty(el, name, value) helper instead of the inline el.name = value; hydratable output keeps setProperty, and spread/assign check in the runtime. The check lives only in @solidjs/web's dev build; the production entry carries a plain node[name] = value stub that no production-compiled module imports, so it tree-shakes away.

    The "Form inputs" section of the DOM RFC, the cheatsheet and the solid-2.0 skill now say that a change to any other dynamic attribute in the same template re-runs a stateful binding, link the new code, and drop equals: false as a way to reject input (it is what the warning reports); write the accepted value back with e.currentTarget.value = next instead.

  • 0117658: A <Loading> that a verdict reader (<Show when={latest(x) ?? x()}>) mounts reveals its content again when a component inside it writes a signal during its mount and creates a memo that reads it and stays pending with no reader: that memo holds nothing and reaches no boundary (#4203, a regression from #4166's fix).

Don't miss a new solid release

NewReleases is sending notifications on new releases.