diff --git a/.changeset/fix-reconcile-array-object-type-mismatch.md b/.changeset/fix-reconcile-array-object-type-mismatch.md new file mode 100644 index 000000000..2c945331f --- /dev/null +++ b/.changeset/fix-reconcile-array-object-type-mismatch.md @@ -0,0 +1,5 @@ +--- +"@solidjs/signals": patch +--- + +reconcile: force wholesale replacement when nested value type changes between array and object diff --git a/packages/solid-signals/src/store/reconcile.ts b/packages/solid-signals/src/store/reconcile.ts index 3cc81ff34..930c88c82 100644 --- a/packages/solid-signals/src/store/reconcile.ts +++ b/packages/solid-signals/src/store/reconcile.ts @@ -170,6 +170,7 @@ function applyStateFast(next: any, target: any, keyFn: (item: NonNullable) !previousValue || !isWrappable(previousValue) || !isWrappable(nextValue) || + Array.isArray(previousValue) !== Array.isArray(nextValue) || (keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue)) ) { tracked && setSignal(tracked, void 0); @@ -314,6 +315,7 @@ function applyStateSlow(next: any, target: any, keyFn: (item: NonNullable) !previousValue || !isWrappable(previousValue) || !isWrappable(nextValue) || + Array.isArray(previousValue) !== Array.isArray(nextValue) || (keyFn(previousValue) != null && keyFn(previousValue) !== keyFn(nextValue)) ) { tracked && setSignal(tracked, void 0); diff --git a/packages/solid-signals/tests/store/reconcile.test.ts b/packages/solid-signals/tests/store/reconcile.test.ts index d4c45557f..70e9aae86 100644 --- a/packages/solid-signals/tests/store/reconcile.test.ts +++ b/packages/solid-signals/tests/store/reconcile.test.ts @@ -1,4 +1,5 @@ import { describe, expect, test } from "vitest"; +import { createMemo, createRoot, flush } from "../../src/index.js"; import { createStore, reconcile, snapshot } from "../../src/index.js"; describe("setState with reconcile", () => { @@ -158,6 +159,23 @@ describe("setState with reconcile", () => { setStore(reconcile({ value: { q: "aa" } }, "id")); expect(store.value).toEqual({ q: "aa" }); }); + + test("Reconcile overwrite tracked array with object updates the signal node", () => { + const [store, setStore] = createStore<{ value: any }>({ value: [1, 2] }); + let derived: any; + + // Establish a tracking subscription on store.value so a signal node is created for it + createRoot(() => { + derived = createMemo(() => store.value); + }); + expect(Array.isArray(derived())).toBe(true); + + setStore(reconcile({ value: { a: 1 } }, "id")); + flush(); + + expect(Array.isArray(derived())).toBe(false); + expect((derived() as any).a).toBe(1); + }); }); // type tests