From 07f84a8f734b90732a71e8e9a734b9e073643651 Mon Sep 17 00:00:00 2001 From: fezproof <26830309+fezproof@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:34:16 +0800 Subject: [PATCH 1/2] Fixed bug with solid not remounting with the right data --- packages/db/src/proxy.ts | 2 + packages/db/tests/proxy.test.ts | 28 +++++ packages/solid-db/tests/useLiveQuery.test.tsx | 115 ++++++++++++++++++ 3 files changed, 145 insertions(+) diff --git a/packages/db/src/proxy.ts b/packages/db/src/proxy.ts index 57723e3cca..34b28ee98f 100644 --- a/packages/db/src/proxy.ts +++ b/packages/db/src/proxy.ts @@ -603,6 +603,8 @@ function deepClone( const symbolProps = Object.getOwnPropertySymbols(obj) for (const sym of symbolProps) { + if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) continue + clone[sym] = deepClone( (obj as Record)[sym], visited, diff --git a/packages/db/tests/proxy.test.ts b/packages/db/tests/proxy.test.ts index bbac0151af..25af05e2a6 100644 --- a/packages/db/tests/proxy.test.ts +++ b/packages/db/tests/proxy.test.ts @@ -101,6 +101,34 @@ describe(`Proxy Library`, () => { }) }) + it(`should not clone non-enumerable symbol metatdata into nested changes`, () => { + const metadata = Symbol(`metadata`) + const domainSymbol = Symbol(`domain`) + + const obj = { + user: { + name: `John`, + age: 30, + [domainSymbol]: `preserved`, + }, + } + + Object.defineProperty(obj.user, metadata, { + value: `internal`, + enumerable: false, + }) + + const changes = withChangeTracking(obj, (draft) => { + draft.user.age = 31 + }) + + const changedUser = changes.user as Record + + expect(changedUser.age).toBe(31) + expect(changedUser[domainSymbol]).toBe(`preserved`) + expect(Object.getOwnPropertySymbols(changedUser)).not.toContain(metadata) + }) + it(`should track when properties are deleted from objects`, () => { const obj: { name: string; age: number; role?: string } = { name: `John`, diff --git a/packages/solid-db/tests/useLiveQuery.test.tsx b/packages/solid-db/tests/useLiveQuery.test.tsx index 378c2a0fc7..e04f38b8e9 100644 --- a/packages/solid-db/tests/useLiveQuery.test.tsx +++ b/packages/solid-db/tests/useLiveQuery.test.tsx @@ -36,6 +36,14 @@ type Issue = { userId: string } +type DeepNode = { + id: string + size: { + min: number + max: number + } +} + const initialPersons: Array = [ { id: `1`, @@ -2587,4 +2595,111 @@ describe(`Query Collections`, () => { expect(rendered.result()).toBeUndefined() }) }) + + it('single-row reflects the current value on remount', async () => { + const nodeId = 'node-1' + const collection = createCollection( + mockSyncCollectionOptions({ + id: `remount-single-nested-test`, + getKey: (item) => item.id, + initialData: [ + { + id: nodeId, + size: { min: 1, max: 10 }, + }, + ], + }), + ) + + const updateMax = (max: number) => { + collection.update(nodeId, (draft) => { + draft.size.max = max + }) + } + + const mount = () => + render(() => { + const query = useLiveQuery((q) => + q + .from({ source: collection }) + .where(({ source }) => eq(source.id, nodeId)) + .findOne(), + ) + + return {query()?.size.max ?? 'pending'} + }) + + const first = mount() + + await waitFor(() => + expect(first.getByTestId(`value`).textContent).toBe(`10`), + ) + + updateMax(777) + + await waitFor(() => + expect(first.getByTestId(`value`).textContent).toBe(`777`), + ) + + first.unmount() + + const second = mount() + await waitFor(() => + expect(second.getByTestId(`value`).textContent).toBe(`777`), + ) + }) + + it('array reflects the current value on remount', async () => { + const nodeId = 'node-1' + const collection = createCollection( + mockSyncCollectionOptions({ + id: `remount-single-nested-test`, + getKey: (item) => item.id, + initialData: [ + { + id: nodeId, + size: { min: 1, max: 10 }, + }, + ], + }), + ) + + const updateMax = (max: number) => { + collection.update(nodeId, (draft) => { + draft.size.max = max + }) + } + + const mount = () => + render(() => { + const query = useLiveQuery((q) => + q + .from({ source: collection }) + .where(({ source }) => eq(source.id, nodeId)), + ) + + return ( + {query()[0]?.size.max ?? 'pending'} + ) + }) + + const first = mount() + + await waitFor(() => + expect(first.getByTestId(`value`).textContent).toBe(`10`), + ) + + updateMax(777) + + await waitFor(() => + expect(first.getByTestId(`value`).textContent).toBe(`777`), + ) + + first.unmount() + + const second = mount() + await waitFor(() => + expect(second.getByTestId(`value`).textContent).toBe(`777`), + ) + }) }) From afe89c68567d90df0b39e35874c02c2efeaede82 Mon Sep 17 00:00:00 2001 From: fezproof <26830309+fezproof@users.noreply.github.com> Date: Mon, 22 Jun 2026 23:36:54 +0800 Subject: [PATCH 2/2] Changeset --- .changeset/loose-flies-sell.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/loose-flies-sell.md diff --git a/.changeset/loose-flies-sell.md b/.changeset/loose-flies-sell.md new file mode 100644 index 0000000000..4cd2e2f2c5 --- /dev/null +++ b/.changeset/loose-flies-sell.md @@ -0,0 +1,6 @@ +--- +'@tanstack/solid-db': patch +'@tanstack/db': patch +--- + +Fixed a bug where cloning non enumerable properties causes solid store to get stuck in a non current state