Skip to content

Commit 5c3b679

Browse files
committed
feat: add support for concurrent mode
1 parent c921585 commit 5c3b679

23 files changed

+938
-554
lines changed

docs/src/pages/guides/placeholder-query-data.md

+3-6
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,12 @@ function Todos() {
2828

2929
### Placeholder Data as a Function
3030

31-
If the process for accessing a query's placeholder data is intensive or just not something you want to perform on every render, you can pass a function as the `placeholderData` value. This function will be executed only once when the query is placeholderized, saving you precious memory and/or CPU:
31+
If the process for accessing a query's placeholder data is intensive or just not something you want to perform on every render, you can memoize the value or pass a memoized function as the `placeholderData` value:
3232

3333
```js
3434
function Todos() {
35-
const result = useQuery('todos', () => fetch('/todos'), {
36-
placeholderData: () => {
37-
return generateFakeTodos()
38-
},
39-
})
35+
const placeholderData = useMemo(() => generateFakeTodos(), [])
36+
const result = useQuery('todos', () => fetch('/todos'), { placeholderData })
4037
}
4138
```
4239

src/core/infiniteQueryObserver.ts

+11-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
hasPreviousPage,
1313
infiniteQueryBehavior,
1414
} from './infiniteQueryBehavior'
15+
import { Query } from './query'
1516

1617
type InfiniteQueryObserverListener<TData, TError> = (
1718
result: InfiniteQueryObserverResult<TData, TError>
@@ -98,9 +99,17 @@ export class InfiniteQueryObserver<
9899
})
99100
}
100101

101-
protected getNewResult(): InfiniteQueryObserverResult<TData, TError> {
102+
protected createResult(
103+
query: Query<TQueryFnData, TError, InfiniteData<TQueryData>>,
104+
options: InfiniteQueryObserverOptions<
105+
TQueryFnData,
106+
TError,
107+
TData,
108+
TQueryData
109+
>
110+
): InfiniteQueryObserverResult<TData, TError> {
102111
const { state } = this.getCurrentQuery()
103-
const result = super.getNewResult()
112+
const result = super.createResult(query, options)
104113
return {
105114
...result,
106115
fetchNextPage: this.fetchNextPage,

src/core/mutationObserver.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import type {
77
MutationObserverResult,
88
MutationObserverOptions,
99
} from './types'
10-
import { getStatusProps } from './utils'
1110

1211
// TYPES
1312

@@ -132,7 +131,10 @@ export class MutationObserver<
132131

133132
this.currentResult = {
134133
...state,
135-
...getStatusProps(state.status),
134+
isLoading: state.status === 'loading',
135+
isSuccess: state.status === 'success',
136+
isError: state.status === 'error',
137+
isIdle: state.status === 'idle',
136138
mutate: this.mutate,
137139
reset: this.reset,
138140
}

src/core/queriesObserver.ts

+64-34
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { difference, getQueryKeyHashFn, replaceAt } from './utils'
22
import { notifyManager } from './notifyManager'
33
import type { QueryObserverOptions, QueryObserverResult } from './types'
44
import type { QueryClient } from './queryClient'
5-
import { QueryObserver } from './queryObserver'
5+
import { NotifyOptions, QueryObserver } from './queryObserver'
66
import { Subscribable } from './subscribable'
77

88
type QueriesObserverListener = (result: QueryObserverResult[]) => void
@@ -20,9 +20,7 @@ export class QueriesObserver extends Subscribable<QueriesObserverListener> {
2020
this.queries = queries || []
2121
this.result = []
2222
this.observers = []
23-
24-
// Subscribe to queries
25-
this.updateObservers()
23+
this.setQueries(this.queries)
2624
}
2725

2826
protected onSubscribe(): void {
@@ -48,21 +46,21 @@ export class QueriesObserver extends Subscribable<QueriesObserverListener> {
4846
})
4947
}
5048

51-
setQueries(queries: QueryObserverOptions[]): void {
49+
setQueries(
50+
queries: QueryObserverOptions[],
51+
notifyOptions?: NotifyOptions
52+
): void {
5253
this.queries = queries
53-
this.updateObservers()
54+
this.updateObservers(notifyOptions)
5455
}
5556

5657
getCurrentResult(): QueryObserverResult[] {
5758
return this.result
5859
}
5960

60-
private updateObservers(): void {
61-
let hasIndexChange = false
62-
63-
const prevObservers = this.observers
64-
const newObservers = this.queries.map((options, i) => {
65-
let observer: QueryObserver | undefined = prevObservers[i]
61+
getOptimisticResult(queries: QueryObserverOptions[]): QueryObserverResult[] {
62+
return queries.map((options, i) => {
63+
let observer: QueryObserver | undefined = this.observers[i]
6664

6765
const defaultedOptions = this.client.defaultQueryObserverOptions(options)
6866
const hashFn = getQueryKeyHashFn(defaultedOptions)
@@ -72,42 +70,74 @@ export class QueriesObserver extends Subscribable<QueriesObserverListener> {
7270
!observer ||
7371
observer.getCurrentQuery().queryHash !== defaultedOptions.queryHash
7472
) {
75-
hasIndexChange = true
76-
observer = prevObservers.find(
73+
observer = this.observers.find(
7774
x => x.getCurrentQuery().queryHash === defaultedOptions.queryHash
7875
)
7976
}
8077

81-
if (observer) {
82-
observer.setOptions(defaultedOptions)
83-
return observer
78+
if (!observer) {
79+
observer = new QueryObserver(this.client, defaultedOptions)
8480
}
8581

86-
return new QueryObserver(this.client, defaultedOptions)
82+
return observer.getOptimisticResult(defaultedOptions)
8783
})
84+
}
8885

89-
if (prevObservers.length === newObservers.length && !hasIndexChange) {
90-
return
91-
}
86+
private updateObservers(notifyOptions?: NotifyOptions): void {
87+
notifyManager.batch(() => {
88+
let hasIndexChange = false
9289

93-
this.observers = newObservers
94-
this.result = newObservers.map(observer => observer.getCurrentResult())
90+
const prevObservers = this.observers
91+
const newObservers = this.queries.map((options, i) => {
92+
let observer: QueryObserver | undefined = prevObservers[i]
9593

96-
if (!this.listeners.length) {
97-
return
98-
}
94+
const defaultedOptions = this.client.defaultQueryObserverOptions(
95+
options
96+
)
97+
const hashFn = getQueryKeyHashFn(defaultedOptions)
98+
defaultedOptions.queryHash = hashFn(defaultedOptions.queryKey!)
99+
100+
if (
101+
!observer ||
102+
observer.getCurrentQuery().queryHash !== defaultedOptions.queryHash
103+
) {
104+
hasIndexChange = true
105+
observer = prevObservers.find(
106+
x => x.getCurrentQuery().queryHash === defaultedOptions.queryHash
107+
)
108+
}
109+
110+
if (observer) {
111+
observer.setOptions(defaultedOptions, notifyOptions)
112+
return observer
113+
}
114+
115+
return new QueryObserver(this.client, defaultedOptions)
116+
})
99117

100-
difference(prevObservers, newObservers).forEach(observer => {
101-
observer.destroy()
102-
})
118+
if (prevObservers.length === newObservers.length && !hasIndexChange) {
119+
return
120+
}
103121

104-
difference(newObservers, prevObservers).forEach(observer => {
105-
observer.subscribe(result => {
106-
this.onUpdate(observer, result)
122+
this.observers = newObservers
123+
this.result = newObservers.map(observer => observer.getCurrentResult())
124+
125+
if (!this.listeners.length) {
126+
return
127+
}
128+
129+
difference(prevObservers, newObservers).forEach(observer => {
130+
observer.destroy()
107131
})
108-
})
109132

110-
this.notify()
133+
difference(newObservers, prevObservers).forEach(observer => {
134+
observer.subscribe(result => {
135+
this.onUpdate(observer, result)
136+
})
137+
})
138+
139+
this.notify()
140+
})
111141
}
112142

113143
private onUpdate(observer: QueryObserver, result: QueryObserverResult): void {

src/core/query.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ export class Query<
260260
}
261261

262262
onFocus(): void {
263-
const observer = this.observers.find(x => x.willFetchOnWindowFocus())
263+
const observer = this.observers.find(x => x.shouldFetchOnWindowFocus())
264264

265265
if (observer) {
266266
observer.refetch()
@@ -271,7 +271,7 @@ export class Query<
271271
}
272272

273273
onOnline(): void {
274-
const observer = this.observers.find(x => x.willFetchOnReconnect())
274+
const observer = this.observers.find(x => x.shouldFetchOnReconnect())
275275

276276
if (observer) {
277277
observer.refetch()
@@ -328,14 +328,15 @@ export class Query<
328328
options?: QueryOptions<TQueryFnData, TError, TData>,
329329
fetchOptions?: FetchOptions
330330
): Promise<TData> {
331-
if (this.state.isFetching)
331+
if (this.state.isFetching) {
332332
if (this.state.dataUpdatedAt && fetchOptions?.cancelRefetch) {
333333
// Silently cancel current fetch if the user wants to cancel refetches
334334
this.cancel({ silent: true })
335335
} else if (this.promise) {
336336
// Return current promise if we are already fetching
337337
return this.promise
338338
}
339+
}
339340

340341
// Update config if passed, otherwise the config from the last execution is used
341342
if (options) {

src/core/queryCache.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,8 @@ export class QueryCache extends Subscribable<QueryCacheListener> {
4242
options: QueryOptions<TQueryFnData, TError, TData>,
4343
state?: QueryState<TData, TError>
4444
): Query<TQueryFnData, TError, TData> {
45-
const hashFn = getQueryKeyHashFn(options)
4645
const queryKey = options.queryKey!
47-
const queryHash = options.queryHash ?? hashFn(queryKey)
46+
const queryHash = options.queryHash ?? getQueryKeyHashFn(options)(queryKey)
4847
let query = this.get<TQueryFnData, TError, TData>(queryHash)
4948

5049
if (!query) {

src/core/queryClient.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
parseFilterArgs,
77
parseQueryArgs,
88
partialMatchKey,
9+
getQueryKeyHashFn,
910
} from './utils'
1011
import type {
1112
DefaultOptions,
@@ -446,12 +447,21 @@ export class QueryClient {
446447
if (options?._defaulted) {
447448
return options
448449
}
449-
return {
450+
451+
const defaultedOptions = {
450452
...this.defaultOptions.queries,
451453
...this.getQueryDefaults(options?.queryKey),
452454
...options,
453455
_defaulted: true,
454456
} as T
457+
458+
if (!defaultedOptions.queryHash && defaultedOptions.queryKey) {
459+
defaultedOptions.queryHash = getQueryKeyHashFn(defaultedOptions)(
460+
defaultedOptions.queryKey
461+
)
462+
}
463+
464+
return defaultedOptions
455465
}
456466

457467
defaultQueryObserverOptions<

0 commit comments

Comments
 (0)