From 2f5b0f5bc1bcfbe897d19dcb853ea504d0a098ec Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sun, 15 Feb 2026 02:05:51 -0800 Subject: [PATCH 1/2] fix: lazily create inTransition external source to prevent use-after-dispose The inTransition external source was eagerly created and disposed after each transition resolved. When a subsequent transition started, the disposed source was still referenced, causing errors when .track() was called on it. This change makes inTransition lazily initialized on first use during a transition and properly re-created after disposal. Fixes #2275. --- packages/solid/src/reactive/signal.ts | 15 +++++++-- packages/solid/test/external-source.spec.ts | 35 ++++++++++++++++++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/solid/src/reactive/signal.ts b/packages/solid/src/reactive/signal.ts index ddce76160..2525b3fa0 100644 --- a/packages/solid/src/reactive/signal.ts +++ b/packages/solid/src/reactive/signal.ts @@ -1471,12 +1471,21 @@ function createComputation( const [track, trigger] = createSignal(undefined, { equals: false }); const ordinary = ExternalSourceConfig.factory(c.fn, trigger); onCleanup(() => ordinary.dispose()); + let inTransition: ExternalSource | undefined; const triggerInTransition: () => void = () => - startTransition(trigger).then(() => inTransition.dispose()); - const inTransition = ExternalSourceConfig.factory(c.fn, triggerInTransition); + startTransition(trigger).then(() => { + if (inTransition) { + inTransition.dispose(); + inTransition = undefined; + } + }); c.fn = x => { track(); - return Transition && Transition.running ? inTransition.track(x) : ordinary.track(x); + if (Transition && Transition.running) { + if (!inTransition) inTransition = ExternalSourceConfig!.factory(c.fn!, triggerInTransition); + return inTransition.track(x); + } + return ordinary.track(x); }; } diff --git a/packages/solid/test/external-source.spec.ts b/packages/solid/test/external-source.spec.ts index 10208a424..9f229db6e 100644 --- a/packages/solid/test/external-source.spec.ts +++ b/packages/solid/test/external-source.spec.ts @@ -1,5 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { createRoot, createMemo, untrack, enableExternalSource } from "../src/index.js"; +import { createRoot, createMemo, createSignal, untrack, enableExternalSource, startTransition } from "../src/index.js"; +import { getSuspenseContext } from "../src/reactive/signal.js"; import "./MessageChannel"; @@ -87,6 +88,38 @@ describe("external source", () => { }); }); + + it("should not throw when rerunning external source in a new transition after disposal", async () => { + // Initialize SuspenseContext so startTransition creates a real Transition + getSuspenseContext(); + + await createRoot(async dispose => { + const e = new ExternalSource(0); + const [signal, setSignal] = createSignal(0); + const memo = createMemo(() => { + return e.get() + signal(); + }); + expect(memo()).toBe(0); + + // First transition: triggers inTransition creation and subsequent disposal + await startTransition(() => { + setSignal(1); + }); + + // Wait for transition to complete and inTransition to be disposed + await new Promise(r => setTimeout(r, 50)); + + // Second transition: should lazily recreate inTransition, not throw on disposed one + await expect( + startTransition(() => { + setSignal(2); + }) + ).resolves.not.toThrow(); + + dispose(); + }); + }); + afterEach(() => { vi.resetModules(); }); From 166069042c4659cf34b2fd89d5929c3a51d04a71 Mon Sep 17 00:00:00 2001 From: Varun Chawla Date: Sun, 15 Feb 2026 02:23:08 -0800 Subject: [PATCH 2/2] add changeset for external source transition fix Co-Authored-By: Claude Opus 4.6 --- .changeset/fix-external-source-transition.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-external-source-transition.md diff --git a/.changeset/fix-external-source-transition.md b/.changeset/fix-external-source-transition.md new file mode 100644 index 000000000..241d0022b --- /dev/null +++ b/.changeset/fix-external-source-transition.md @@ -0,0 +1,5 @@ +--- +"solid-js": patch +--- + +fix: lazily create inTransition external source to prevent use-after-dispose