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..1e25eb6255a 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,63 @@ 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', + }) + }) }) 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(