From d47e3e9df848402bb68f14bba7a5dfb46e6b821f Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Fri, 4 Sep 2026 14:53:53 +0500 Subject: [PATCH 1/2] fix(broadcast-client): don't overwrite an existing query's data on an incoming 'added' message --- ...roadcast-added-overwrites-existing-data.md | 5 ++ .../src/__tests__/index.test.ts | 79 +++++++++++++++++++ .../src/index.ts | 4 +- 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-broadcast-added-overwrites-existing-data.md diff --git a/.changeset/fix-broadcast-added-overwrites-existing-data.md b/.changeset/fix-broadcast-added-overwrites-existing-data.md new file mode 100644 index 00000000000..2c881d7c148 --- /dev/null +++ b/.changeset/fix-broadcast-added-overwrites-existing-data.md @@ -0,0 +1,5 @@ +--- +'@tanstack/query-broadcast-client-experimental': patch +--- + +fix: don't overwrite an existing query's resolved data when another tab broadcasts an `added` event for it diff --git a/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts b/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts index 73a8b0c914b..439f873d5cb 100644 --- a/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts +++ b/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts @@ -122,6 +122,85 @@ describe('broadcastQueryClient', () => { expect(mockPostMessage).toHaveBeenCalled() }) + + it('should not overwrite an existing query with data when an "added" message arrives for it', () => { + const existingKey = queryKey() + + broadcastQueryClient({ + queryClient, + broadcastChannel: 'test_channel', + }) + + // A query that already resolved in this tab (e.g. it fetched before + // another tab mounted the same key). + queryClient.setQueryData(existingKey, { value: 'resolved' }) + const existingQuery = queryCache.find({ queryKey: existingKey })! + + // Another tab just mounted the same key for the first time, so its + // `build()` broadcasts an `added` message with a pending state and + // no data. + lastCreatedChannel.onmessage?.({ + type: 'added', + queryHash: existingQuery.queryHash, + queryKey: existingKey, + state: { status: 'pending', data: undefined }, + }) + + expect(queryClient.getQueryData(existingKey)).toEqual({ + value: 'resolved', + }) + expect(queryClient.getQueryState(existingKey)?.status).toBe('success') + }) + + it('should adopt an "added" message\'s state for an existing query that has no data yet', () => { + const existingKey = queryKey() + + broadcastQueryClient({ + queryClient, + broadcastChannel: 'test_channel', + }) + + // This tab has already built the query (e.g. an observer mounted it) + // but hasn't fetched it yet. + const existingQuery = queryCache.build(queryClient, { + queryKey: existingKey, + }) + + // Another tab already had this key resolved (e.g. via `initialData`) + // when it mounted, so its `added` message carries real data. + lastCreatedChannel.onmessage?.({ + type: 'added', + queryHash: existingQuery.queryHash, + queryKey: existingKey, + state: { status: 'success', data: { value: 'from other tab' } }, + }) + + expect(queryClient.getQueryData(existingKey)).toEqual({ + value: 'from other tab', + }) + }) + + it('should build a new query with the broadcasted state when an "added" message arrives for an unknown key', () => { + const newKey = queryKey() + + broadcastQueryClient({ + queryClient, + broadcastChannel: 'test_channel', + }) + + // A tab mounting this key for the very first time in the whole app + // already has resolved data for it (e.g. via `initialData`). + lastCreatedChannel.onmessage?.({ + type: 'added', + queryHash: JSON.stringify(newKey), + queryKey: newKey, + state: { status: 'success', data: { value: 'brand new' } }, + }) + + expect(queryClient.getQueryData(newKey)).toEqual({ + value: 'brand new', + }) + }) }) describe('postMessage error handling', () => { diff --git a/packages/query-broadcast-client-experimental/src/index.ts b/packages/query-broadcast-client-experimental/src/index.ts index b1f16fb4160..9e357a7116e 100644 --- a/packages/query-broadcast-client-experimental/src/index.ts +++ b/packages/query-broadcast-client-experimental/src/index.ts @@ -175,7 +175,9 @@ export function broadcastQueryClient({ } } else if (type === 'added') { if (query) { - query.setState(state) + if (query.state.data === undefined) { + query.setState(state) + } return } queryCache.build( From 0a73db4c255eb44e1889696f8d5a9ddf57934229 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Fri, 4 Sep 2026 14:57:53 +0500 Subject: [PATCH 2/2] test(query-broadcast-client-experimental): drop test that duplicates existing 'added' build coverage --- .../src/__tests__/index.test.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts b/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts index 439f873d5cb..1e25eb6255a 100644 --- a/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts +++ b/packages/query-broadcast-client-experimental/src/__tests__/index.test.ts @@ -179,28 +179,6 @@ describe('broadcastQueryClient', () => { value: 'from other tab', }) }) - - it('should build a new query with the broadcasted state when an "added" message arrives for an unknown key', () => { - const newKey = queryKey() - - broadcastQueryClient({ - queryClient, - broadcastChannel: 'test_channel', - }) - - // A tab mounting this key for the very first time in the whole app - // already has resolved data for it (e.g. via `initialData`). - lastCreatedChannel.onmessage?.({ - type: 'added', - queryHash: JSON.stringify(newKey), - queryKey: newKey, - state: { status: 'success', data: { value: 'brand new' } }, - }) - - expect(queryClient.getQueryData(newKey)).toEqual({ - value: 'brand new', - }) - }) }) describe('postMessage error handling', () => {