From b1e9b7739a96e7dfb415e77f02162bf7b1fdb626 Mon Sep 17 00:00:00 2001 From: "Idris M. Celik" Date: Sun, 13 Sep 2026 11:22:30 +0000 Subject: [PATCH] fix(svelte-store): preserve selector object identity --- .changeset/svelte-selector-identity.md | 5 +++ .../svelte-store/src/useSelector.svelte.ts | 2 +- .../tests/ObjectSelector.test.svelte | 35 +++++++++++++++++++ packages/svelte-store/tests/index.test.ts | 10 ++++++ 4 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 .changeset/svelte-selector-identity.md create mode 100644 packages/svelte-store/tests/ObjectSelector.test.svelte diff --git a/.changeset/svelte-selector-identity.md b/.changeset/svelte-selector-identity.md new file mode 100644 index 00000000..28948b90 --- /dev/null +++ b/.changeset/svelte-selector-identity.md @@ -0,0 +1,5 @@ +--- +'@tanstack/svelte-store': patch +--- + +Fix `useSelector` object selection comparisons by preserving raw value identity. diff --git a/packages/svelte-store/src/useSelector.svelte.ts b/packages/svelte-store/src/useSelector.svelte.ts index 82a72fb1..c7e08566 100644 --- a/packages/svelte-store/src/useSelector.svelte.ts +++ b/packages/svelte-store/src/useSelector.svelte.ts @@ -35,7 +35,7 @@ export function useSelector>( options: UseSelectorOptions = {}, ): { readonly current: TSelected } { const compare = options.compare ?? defaultCompare - let slice = $state(selector(source.get())) + let slice = $state.raw(selector(source.get())) $effect(() => { const unsub = source.subscribe((s) => { diff --git a/packages/svelte-store/tests/ObjectSelector.test.svelte b/packages/svelte-store/tests/ObjectSelector.test.svelte new file mode 100644 index 00000000..516a677a --- /dev/null +++ b/packages/svelte-store/tests/ObjectSelector.test.svelte @@ -0,0 +1,35 @@ + + +
+

Number rendered: {renderCount}

+

Selected: {selected.current.value}

+ +
\ No newline at end of file diff --git a/packages/svelte-store/tests/index.test.ts b/packages/svelte-store/tests/index.test.ts index dc304f01..641cbe4d 100644 --- a/packages/svelte-store/tests/index.test.ts +++ b/packages/svelte-store/tests/index.test.ts @@ -3,6 +3,7 @@ import { render, waitFor } from '@testing-library/svelte' import { userEvent } from '@testing-library/user-event' import { shallow } from '../src/index.svelte.js' import TestBaseStore from './BaseStore.test.svelte' +import TestObjectSelector from './ObjectSelector.test.svelte' import TestRerender from './Render.test.svelte' import TestValue from './Value.test.svelte' @@ -28,6 +29,15 @@ describe('useSelector', () => { expect(getByText('Number rendered: 2')).toBeInTheDocument() }) + it('preserves object identity for unchanged selector state', async () => { + const { getByRole, getByText } = render(TestObjectSelector) + expect(getByText('Number rendered: 1')).toBeInTheDocument() + + await user.click(getByRole('button', { name: 'Update ignored' })) + + expect(getByText('Number rendered: 1')).toBeInTheDocument() + }) + it('useSelector reads writable and readonly store state', async () => { const { getByText } = render(TestValue) expect(getByText('Value: 1')).toBeInTheDocument()