Skip to content

Commit 9fcd5f0

Browse files
committed
fix: make sure setQueryData is not considered as initial data
1 parent 1117a51 commit 9fcd5f0

File tree

7 files changed

+65
-16
lines changed

7 files changed

+65
-16
lines changed

docs/src/pages/docs/api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ const queryInfo = useQuery({
8383
- A function like `attempt => Math.min(attempt > 1 ? 2 ** attempt * 1000 : 1000, 30 * 1000)` applies exponential backoff.
8484
- A function like `attempt => attempt * 1000` applies linear backoff.
8585
- `staleTime: Int | Infinity`
86-
- The time in milliseconds that cache data remains fresh. After a successful cache update, that cache data will become stale after this duration.
86+
- The time in milliseconds after data is considered stale.
8787
- If set to `Infinity`, query will never go stale
8888
- `cacheTime: Int | Infinity`
8989
- The time in milliseconds that unused/inactive cache data remains in memory. When a query's cache becomes unused or inactive, that cache data will be garbage collected after this duration.

src/core/query.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export interface QueryState<TResult, TError> {
4545
isFetching: boolean
4646
isFetchingMore: IsFetchingMoreValue
4747
isIdle: boolean
48+
isInitialData: boolean
4849
isLoading: boolean
4950
isSuccess: boolean
5051
status: QueryStatus
@@ -594,6 +595,7 @@ function getDefaultState<TResult, TError>(
594595
isFetched: false,
595596
isFetching: initialStatus === QueryStatus.Loading,
596597
isFetchingMore: false,
598+
isInitialData: config.isInitialData !== false,
597599
failureCount: 0,
598600
fetchedCount: 0,
599601
data: initialData,
@@ -634,6 +636,7 @@ export function queryReducer<TResult, TError>(
634636
isFetched: true,
635637
isFetching: false,
636638
isFetchingMore: false,
639+
isInitialData: false,
637640
canFetchMore: action.canFetchMore,
638641
updatedAt: Date.now(),
639642
failureCount: 0,

src/core/queryCache.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ export class QueryCache {
339339
}
340340

341341
this.buildQuery<TResult, TError>(queryKey, {
342-
initialStale: typeof config?.staleTime === 'undefined',
342+
isInitialData: false,
343343
initialData: functionalUpdate(updater, undefined),
344344
...config,
345345
})

src/core/queryObserver.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,11 +244,9 @@ export class QueryObserver<TResult, TError> {
244244
isPreviousData = true
245245
}
246246

247-
let isStale = false
247+
let isStale
248248

249-
// When the query has not been fetched yet and this is the initial render,
250-
// determine the staleness based on the initialStale or existence of initial data.
251-
if (!currentResult && !state.isFetched) {
249+
if (state.isInitialData && !currentResult) {
252250
if (typeof config.initialStale === 'function') {
253251
isStale = config.initialStale()
254252
} else if (typeof config.initialStale === 'boolean') {

src/core/types.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,14 @@ export interface BaseQueryConfig<TResult, TError = unknown, TData = TResult> {
4242
*/
4343
retry?: boolean | number | ((failureCount: number, error: TError) => boolean)
4444
retryDelay?: number | ((retryAttempt: number) => number)
45-
staleTime?: number
4645
cacheTime?: number
4746
isDataEqual?: (oldData: unknown, newData: unknown) => boolean
4847
queryFn?: QueryFunction<TData>
4948
queryKey?: QueryKey
5049
queryKeySerializerFn?: QueryKeySerializerFunction
5150
queryFnParamsFilter?: (args: ArrayQueryKey) => ArrayQueryKey
5251
initialData?: TResult | InitialDataFunction<TResult>
53-
initialStale?: boolean | InitialStaleFunction
52+
isInitialData?: boolean
5453
infinite?: true
5554
/**
5655
* Set this to `false` to disable structural sharing between query results.
@@ -75,6 +74,17 @@ export interface QueryObserverConfig<
7574
* Defaults to `true`.
7675
*/
7776
enabled?: boolean | unknown
77+
/**
78+
* The time in milliseconds after data is considered stale.
79+
* If set to `Infinity`, the data will never be stale.
80+
*/
81+
staleTime?: number
82+
/**
83+
* If set, this will mark any `initialData` provided as stale and will likely cause it to be refetched on mount.
84+
* If a function is passed, it will be called only when appropriate to resolve the `initialStale` value.
85+
* This can be useful if your `initialStale` value is costly to calculate.
86+
*/
87+
initialStale?: boolean | InitialStaleFunction
7888
/**
7989
* If set to a number, the query will continuously refetch at this frequency in milliseconds.
8090
* Defaults to `false`.

src/hydration/hydration.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,9 @@ export function hydrate<TResult>(
8383

8484
for (const dehydratedQuery of queries) {
8585
const queryKey = dehydratedQuery.config.queryKey
86-
const queryConfig: QueryConfig<TResult> = dehydratedQuery.config as QueryConfig<
87-
TResult
88-
>
89-
86+
const queryConfig = dehydratedQuery.config as QueryConfig<TResult>
9087
const query = queryCache.buildQuery(queryKey, queryConfig)
88+
query.state.isInitialData = false
9189
query.state.updatedAt = dehydratedQuery.updatedAt
9290
}
9391
}

src/react/tests/useQuery.test.tsx

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,41 @@ describe('useQuery', () => {
12251225
consoleMock.mockRestore()
12261226
})
12271227

1228+
it('should fetch on mount when a query was already created with setQueryData', async () => {
1229+
const key = queryKey()
1230+
const states: QueryResult<string>[] = []
1231+
1232+
queryCache.setQueryData(key, 'prefetched')
1233+
1234+
function Page() {
1235+
const state = useQuery(key, () => 'data')
1236+
states.push(state)
1237+
return null
1238+
}
1239+
1240+
render(<Page />)
1241+
1242+
await waitFor(() =>
1243+
expect(states).toMatchObject([
1244+
{
1245+
data: 'prefetched',
1246+
isFetching: false,
1247+
isStale: true,
1248+
},
1249+
{
1250+
data: 'prefetched',
1251+
isFetching: true,
1252+
isStale: true,
1253+
},
1254+
{
1255+
data: 'data',
1256+
isFetching: false,
1257+
isStale: true,
1258+
},
1259+
])
1260+
)
1261+
})
1262+
12281263
it('should refetch after focus regain', async () => {
12291264
const key = queryKey()
12301265
const states: QueryResult<string>[] = []
@@ -1245,29 +1280,34 @@ describe('useQuery', () => {
12451280

12461281
render(<Page />)
12471282

1248-
await waitFor(() => expect(states.length).toBe(2))
1283+
await waitFor(() => expect(states.length).toBe(3))
12491284

12501285
act(() => {
12511286
// reset visibilityState to original value
12521287
mockVisibilityState(originalVisibilityState)
12531288
window.dispatchEvent(new FocusEvent('focus'))
12541289
})
12551290

1256-
await waitFor(() => expect(states.length).toBe(4))
1291+
await waitFor(() => expect(states.length).toBe(5))
12571292

12581293
expect(states).toMatchObject([
12591294
{
12601295
data: 'prefetched',
12611296
isFetching: false,
1262-
isStale: false,
1297+
isStale: true,
12631298
},
12641299
{
12651300
data: 'prefetched',
1301+
isFetching: true,
1302+
isStale: true,
1303+
},
1304+
{
1305+
data: 'data',
12661306
isFetching: false,
12671307
isStale: true,
12681308
},
12691309
{
1270-
data: 'prefetched',
1310+
data: 'data',
12711311
isFetching: true,
12721312
isStale: true,
12731313
},

0 commit comments

Comments
 (0)