Skip to content

Commit 51aad7d

Browse files
authored
fix(query-core): race condition in StrictMode (#9565)
1 parent de3626a commit 51aad7d

File tree

2 files changed

+20
-11
lines changed

2 files changed

+20
-11
lines changed

packages/query-core/src/query.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,13 @@ export class Query<
376376
options?: QueryOptions<TQueryFnData, TError, TData, TQueryKey>,
377377
fetchOptions?: FetchOptions<TQueryFnData>,
378378
): Promise<TData> {
379-
if (this.state.fetchStatus !== 'idle') {
379+
if (
380+
this.state.fetchStatus !== 'idle' &&
381+
// If the promise in the retyer is already rejected, we have to definitely
382+
// re-start the fetch; there is a chance that the query is still in a
383+
// pending state when that happens
384+
this.#retryer?.status() !== 'rejected'
385+
) {
380386
if (this.state.data !== undefined && fetchOptions?.cancelRefetch) {
381387
// Silently cancel current fetch if the user wants to cancel refetch
382388
this.cancel({ silent: true })

packages/query-core/src/retryer.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { focusManager } from './focusManager'
22
import { onlineManager } from './onlineManager'
33
import { pendingThenable } from './thenable'
44
import { isServer, sleep } from './utils'
5+
import type { Thenable } from './thenable'
56
import type { CancelOptions, DefaultError, NetworkMode } from './types'
67

78
// TYPES
@@ -27,6 +28,7 @@ export interface Retryer<TData = unknown> {
2728
continueRetry: () => void
2829
canStart: () => boolean
2930
start: () => Promise<TData>
31+
status: () => 'pending' | 'resolved' | 'rejected'
3032
}
3133

3234
export type RetryValue<TError> = boolean | number | ShouldRetryFunction<TError>
@@ -75,13 +77,15 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
7577
): Retryer<TData> {
7678
let isRetryCancelled = false
7779
let failureCount = 0
78-
let isResolved = false
7980
let continueFn: ((value?: unknown) => void) | undefined
8081

8182
const thenable = pendingThenable<TData>()
8283

84+
const isResolved = () =>
85+
(thenable.status as Thenable<TData>['status']) !== 'pending'
86+
8387
const cancel = (cancelOptions?: CancelOptions): void => {
84-
if (!isResolved) {
88+
if (!isResolved()) {
8589
reject(new CancelledError(cancelOptions))
8690

8791
config.abort?.()
@@ -103,16 +107,14 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
103107
const canStart = () => canFetch(config.networkMode) && config.canRun()
104108

105109
const resolve = (value: any) => {
106-
if (!isResolved) {
107-
isResolved = true
110+
if (!isResolved()) {
108111
continueFn?.()
109112
thenable.resolve(value)
110113
}
111114
}
112115

113116
const reject = (value: any) => {
114-
if (!isResolved) {
115-
isResolved = true
117+
if (!isResolved()) {
116118
continueFn?.()
117119
thenable.reject(value)
118120
}
@@ -121,14 +123,14 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
121123
const pause = () => {
122124
return new Promise((continueResolve) => {
123125
continueFn = (value) => {
124-
if (isResolved || canContinue()) {
126+
if (isResolved() || canContinue()) {
125127
continueResolve(value)
126128
}
127129
}
128130
config.onPause?.()
129131
}).then(() => {
130132
continueFn = undefined
131-
if (!isResolved) {
133+
if (!isResolved()) {
132134
config.onContinue?.()
133135
}
134136
})
@@ -137,7 +139,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
137139
// Create loop function
138140
const run = () => {
139141
// Do nothing if already resolved
140-
if (isResolved) {
142+
if (isResolved()) {
141143
return
142144
}
143145

@@ -158,7 +160,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
158160
.then(resolve)
159161
.catch((error) => {
160162
// Stop if the fetch is already resolved
161-
if (isResolved) {
163+
if (isResolved()) {
162164
return
163165
}
164166

@@ -203,6 +205,7 @@ export function createRetryer<TData = unknown, TError = DefaultError>(
203205

204206
return {
205207
promise: thenable,
208+
status: () => thenable.status,
206209
cancel,
207210
continue: () => {
208211
continueFn?.()

0 commit comments

Comments
 (0)