diff --git a/src/core/query.ts b/src/core/query.ts index ce70b56dc8..71d833d134 100644 --- a/src/core/query.ts +++ b/src/core/query.ts @@ -1,9 +1,4 @@ -import { - getAbortController, - noop, - replaceEqualDeep, - timeUntilStale, -} from './utils' +import { getAbortController, noop, replaceData, timeUntilStale } from './utils' import type { InitialDataFunction, QueryKey, @@ -195,16 +190,11 @@ export class Query< } } - setData(data: TData, options?: SetDataOptions & { manual: boolean }): TData { - const prevData = this.state.data - - // Use prev data if an isDataEqual function is defined and returns `true` - if (this.options.isDataEqual?.(prevData, data)) { - data = prevData as TData - } else if (this.options.structuralSharing !== false) { - // Structurally share data between prev and new data if needed - data = replaceEqualDeep(prevData, data) - } + setData( + newData: TData, + options?: SetDataOptions & { manual: boolean } + ): TData { + const data = replaceData(this.state.data, newData, this.options) // Set data and mark it as cached this.dispatch({ diff --git a/src/core/queryObserver.ts b/src/core/queryObserver.ts index 68625570b2..46e2e6209d 100644 --- a/src/core/queryObserver.ts +++ b/src/core/queryObserver.ts @@ -3,7 +3,7 @@ import { isServer, isValidTimeout, noop, - replaceEqualDeep, + replaceData, shallowEqualObjects, timeUntilStale, } from './utils' @@ -467,9 +467,7 @@ export class QueryObserver< try { this.selectFn = options.select data = options.select(state.data) - if (options.structuralSharing !== false) { - data = replaceEqualDeep(prevResult?.data, data) - } + data = replaceData(prevResult?.data, data, options) this.selectResult = data this.selectError = null } catch (selectError) { @@ -507,12 +505,11 @@ export class QueryObserver< if (options.select && typeof placeholderData !== 'undefined') { try { placeholderData = options.select(placeholderData) - if (options.structuralSharing !== false) { - placeholderData = replaceEqualDeep( - prevResult?.data, - placeholderData - ) - } + placeholderData = replaceData( + prevResult?.data, + placeholderData, + options + ) this.selectError = null } catch (selectError) { if (process.env.NODE_ENV !== 'production') { diff --git a/src/core/tests/queryObserver.test.tsx b/src/core/tests/queryObserver.test.tsx index bafdf46fda..860d248663 100644 --- a/src/core/tests/queryObserver.test.tsx +++ b/src/core/tests/queryObserver.test.tsx @@ -630,6 +630,35 @@ describe('queryObserver', () => { unsubscribe() }) + test('should prefer isDataEqual to structuralSharing', async () => { + const key = queryKey() + + const data = { value: 'data' } + const newData = { value: 'data' } + + const observer = new QueryObserver(queryClient, { + queryKey: key, + queryFn: () => data, + }) + + const unsubscribe = observer.subscribe(() => undefined) + + await sleep(10) + expect(observer.getCurrentResult().data).toBe(data) + + observer.setOptions({ + queryKey: key, + queryFn: () => newData, + isDataEqual: () => true, + structuralSharing: false, + }) + + await observer.refetch() + expect(observer.getCurrentResult().data).toBe(data) + + unsubscribe() + }) + test('select function error using placeholderdata should log an error', () => { const key = queryKey() diff --git a/src/core/utils.ts b/src/core/utils.ts index ec5c7df778..8fdb107b54 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -413,3 +413,17 @@ export function getAbortController(): AbortController | undefined { return new AbortController() } } + +export function replaceData< + TData, + TOptions extends QueryOptions +>(prevData: TData | undefined, data: TData, options: TOptions): TData { + // Use prev data if an isDataEqual function is defined and returns `true` + if (options.isDataEqual?.(prevData, data)) { + return prevData as TData + } else if (options.structuralSharing !== false) { + // Structurally share data between prev and new data if needed + return replaceEqualDeep(prevData, data) + } + return data +}