diff --git a/.changeset/fix-reconcile-trailing-removal-notify.md b/.changeset/fix-reconcile-trailing-removal-notify.md new file mode 100644 index 000000000..4d78567db --- /dev/null +++ b/.changeset/fix-reconcile-trailing-removal-notify.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +reconcile: notify ownKeys subscribers on pure keyed trailing removal diff --git a/packages/solid-signals/src/store/reconcile.ts b/packages/solid-signals/src/store/reconcile.ts index 3cc81ff34..6ad7d6693 100644 --- a/packages/solid-signals/src/store/reconcile.ts +++ b/packages/solid-signals/src/store/reconcile.ts @@ -99,7 +99,7 @@ function applyStateFast(next: any, target: any, keyFn: (item: NonNullable) applyState(next[j], wrapped, keyFn); } - changed && notifySelf(target); + (changed || prevLength !== next.length) && notifySelf(target); prevLength !== next.length && arrayNodes?.length && setSignal(arrayNodes.length, next.length); @@ -244,7 +244,7 @@ function applyStateSlow(next: any, target: any, keyFn: (item: NonNullable) } const nextLength = next.length; - changed && notifySelf(target); + (changed || prevLength !== nextLength) && notifySelf(target); prevLength !== nextLength && nodes?.length && setSignal(nodes.length, nextLength); return; } diff --git a/packages/solid-signals/tests/store/reconcile.test.ts b/packages/solid-signals/tests/store/reconcile.test.ts index d4c45557f..3e687f438 100644 --- a/packages/solid-signals/tests/store/reconcile.test.ts +++ b/packages/solid-signals/tests/store/reconcile.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { createStore, reconcile, snapshot } from "../../src/index.js"; +import { createStore, reconcile, snapshot, $TRACK, createMemo, createRoot, createEffect, createRenderEffect, flush } from "../../src/index.js"; describe("setState with reconcile", () => { test("Reconcile a simple object", () => { @@ -158,6 +158,27 @@ describe("setState with reconcile", () => { setStore(reconcile({ value: { q: "aa" } }, "id")); expect(store.value).toEqual({ q: "aa" }); }); + test("Reconcile keyed trailing removal notifies $TRACK subscribers", () => { + let effectRunCount = 0; + const [state, setState] = createStore({ arr: [{ id: 1 }, { id: 2 }, { id: 3 }] }); + createRoot(() => { + createRenderEffect(() => { + effectRunCount++; + // accessing $TRACK subscribes to ownKeys notifications on arr + (state.arr as any)[$TRACK]; + return undefined; + }, () => undefined); + }); + // flush to run the effect initially + flush(); + const runsBefore = effectRunCount; + setState(s => { + reconcile([{ id: 1 }, { id: 2 }], "id")(s.arr); + }); + // flush to propagate invalidation and re-run the effect + flush(); + expect(effectRunCount).toBeGreaterThan(runsBefore); + }); }); // type tests