diff --git a/.changeset/quiet-actions-stay.md b/.changeset/quiet-actions-stay.md new file mode 100644 index 00000000..dcddf749 --- /dev/null +++ b/.changeset/quiet-actions-stay.md @@ -0,0 +1,5 @@ +--- +"@solidjs/router": patch +--- + +Prevent stale bound action cleanup from unregistering a newer action with the same URL. diff --git a/src/data/action.ts b/src/data/action.ts index ba952ff0..7fd6a14e 100644 --- a/src/data/action.ts +++ b/src/data/action.ts @@ -210,7 +210,10 @@ function toAction, U, V = T>( fn[invokeSymbol] = invoke; if (!isServer) { actions.set(url, fn as unknown as Action); - getOwner() && onCleanup(() => actions.delete(url)); + getOwner() && + onCleanup(() => { + if (actions.get(url) === fn) actions.delete(url); + }); } return fn as unknown as Action; } diff --git a/test/data/action.spec.ts b/test/data/action.spec.ts index f2f46f00..0b9d3e47 100644 --- a/test/data/action.spec.ts +++ b/test/data/action.spec.ts @@ -88,6 +88,31 @@ describe("action", () => { expect(actions.get(testAction.url)).toBe(testAction); }); + test("should not unregister a replacement action with the same URL", () => { + const testAction = action(async (value: string) => value, "replacement-test"); + let disposeFirst!: () => void; + let disposeSecond!: () => void; + + const first = createRoot(dispose => { + disposeFirst = dispose; + return testAction.with("value"); + }); + const second = createRoot(dispose => { + disposeSecond = dispose; + return testAction.with("value"); + }); + + expect(first.url).toBe(second.url); + expect(actions.get(second.url)).toBe(second); + + disposeFirst(); + + expect(actions.get(second.url)).toBe(second); + + disposeSecond(); + expect(actions.has(second.url)).toBe(false); + }); + test("should support `.with` method for currying arguments", () => { const baseAction = action(async (prefix: string, data: string) => { return `${prefix}: ${data}`;