Skip to content

Commit 7c883eb

Browse files
committed
fix: move thenable-recreation into createResult
`updateResult` will only be called after a fetch, but when we switch between caches without a fetch, we will only call `createResult`; this fix stops `data` from the queryResult and the `thenable` to go out-of-sync; it's backwards compatible because `updateResult` also invokes `createResult`
1 parent babf66f commit 7c883eb

File tree

2 files changed

+111
-21
lines changed

2 files changed

+111
-21
lines changed

packages/query-core/src/queryObserver.ts

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -594,27 +594,7 @@ export class QueryObserver<
594594
promise: this.#currentThenable,
595595
}
596596

597-
return result as QueryObserverResult<TData, TError>
598-
}
599-
600-
updateResult(notifyOptions?: NotifyOptions): void {
601-
const prevResult = this.#currentResult as
602-
| QueryObserverResult<TData, TError>
603-
| undefined
604-
605-
const nextResult = this.createResult(this.#currentQuery, this.options)
606-
607-
this.#currentResultState = this.#currentQuery.state
608-
this.#currentResultOptions = this.options
609-
610-
if (this.#currentResultState.data !== undefined) {
611-
this.#lastQueryWithDefinedData = this.#currentQuery
612-
}
613-
614-
// Only notify and update result if something has changed
615-
if (shallowEqualObjects(nextResult, prevResult)) {
616-
return
617-
}
597+
const nextResult = result as QueryObserverResult<TData, TError>
618598

619599
if (this.options.experimental_prefetchInRender) {
620600
const finalizeThenableIfPossible = (thenable: PendingThenable<TData>) => {
@@ -648,6 +628,7 @@ export class QueryObserver<
648628
nextResult.status === 'error' ||
649629
nextResult.data !== prevThenable.value
650630
) {
631+
console.log('recreating thenable')
651632
recreateThenable()
652633
}
653634
break
@@ -662,6 +643,28 @@ export class QueryObserver<
662643
}
663644
}
664645

646+
return nextResult
647+
}
648+
649+
updateResult(notifyOptions?: NotifyOptions): void {
650+
const prevResult = this.#currentResult as
651+
| QueryObserverResult<TData, TError>
652+
| undefined
653+
654+
const nextResult = this.createResult(this.#currentQuery, this.options)
655+
656+
this.#currentResultState = this.#currentQuery.state
657+
this.#currentResultOptions = this.options
658+
659+
if (this.#currentResultState.data !== undefined) {
660+
this.#lastQueryWithDefinedData = this.#currentQuery
661+
}
662+
663+
// Only notify and update result if something has changed
664+
if (shallowEqualObjects(nextResult, prevResult)) {
665+
return
666+
}
667+
665668
this.#currentResult = nextResult
666669

667670
// Determine which callbacks to trigger

packages/react-query/src/__tests__/useQuery.test.tsx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7385,5 +7385,92 @@ describe('useQuery', () => {
73857385
fireEvent.click(rendered.getByText('enable'))
73867386
await waitFor(() => rendered.getByText('test1'))
73877387
})
7388+
7389+
it('should show correct data when read from cache only (staleTime)', async () => {
7390+
const key = queryKey()
7391+
let suspenseRenderCount = 0
7392+
queryClient.setQueryData(key, 'initial')
7393+
7394+
function MyComponent(props: { promise: Promise<string> }) {
7395+
const data = React.use(props.promise)
7396+
7397+
return <>{data}</>
7398+
}
7399+
7400+
function Loading() {
7401+
suspenseRenderCount++
7402+
return <>loading..</>
7403+
}
7404+
function Page() {
7405+
const query = useQuery({
7406+
queryKey: key,
7407+
queryFn: async () => {
7408+
await sleep(1)
7409+
return 'test'
7410+
},
7411+
staleTime: Infinity,
7412+
})
7413+
7414+
return (
7415+
<React.Suspense fallback={<Loading />}>
7416+
<MyComponent promise={query.promise} />
7417+
</React.Suspense>
7418+
)
7419+
}
7420+
7421+
const rendered = renderWithClient(queryClient, <Page />)
7422+
await waitFor(() => rendered.getByText('initial'))
7423+
7424+
expect(suspenseRenderCount).toBe(0)
7425+
})
7426+
7427+
it('should show correct data when switching between cache entries without re-fetches', async () => {
7428+
const key = queryKey()
7429+
7430+
function MyComponent(props: { promise: Promise<string> }) {
7431+
const data = React.use(props.promise)
7432+
7433+
return <>{data}</>
7434+
}
7435+
7436+
function Loading() {
7437+
return <>loading..</>
7438+
}
7439+
function Page() {
7440+
const [count, setCount] = React.useState(0)
7441+
const query = useQuery({
7442+
queryKey: [key, count],
7443+
queryFn: async () => {
7444+
await sleep(10)
7445+
return 'test' + count
7446+
},
7447+
staleTime: Infinity,
7448+
})
7449+
7450+
return (
7451+
<div>
7452+
<React.Suspense fallback={<Loading />}>
7453+
<MyComponent promise={query.promise} />
7454+
</React.Suspense>
7455+
<button onClick={() => setCount(count + 1)}>inc</button>
7456+
<button onClick={() => setCount(count - 1)}>dec</button>
7457+
</div>
7458+
)
7459+
}
7460+
7461+
const rendered = renderWithClient(queryClient, <Page />)
7462+
await waitFor(() => rendered.getByText('loading..'))
7463+
await waitFor(() => rendered.getByText('test0'))
7464+
7465+
fireEvent.click(rendered.getByText('inc'))
7466+
await waitFor(() => rendered.getByText('loading..'))
7467+
7468+
await waitFor(() => rendered.getByText('test1'))
7469+
7470+
console.log('---------dec------------')
7471+
fireEvent.click(rendered.getByText('dec'))
7472+
7473+
await waitFor(() => rendered.getByText('test0'))
7474+
})
73887475
})
73897476
})

0 commit comments

Comments
 (0)