@tanstack/query-core 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/focusManager.js +101 -0
- package/build/cjs/focusManager.js.map +1 -0
- package/build/cjs/hydration.js +112 -0
- package/build/cjs/hydration.js.map +1 -0
- package/build/cjs/index.js +51 -0
- package/build/cjs/index.js.map +1 -0
- package/build/cjs/infiniteQueryBehavior.js +160 -0
- package/build/cjs/infiniteQueryBehavior.js.map +1 -0
- package/build/cjs/infiniteQueryObserver.js +92 -0
- package/build/cjs/infiniteQueryObserver.js.map +1 -0
- package/build/cjs/logger.js +18 -0
- package/build/cjs/logger.js.map +1 -0
- package/build/cjs/mutation.js +258 -0
- package/build/cjs/mutation.js.map +1 -0
- package/build/cjs/mutationCache.js +99 -0
- package/build/cjs/mutationCache.js.map +1 -0
- package/build/cjs/mutationObserver.js +130 -0
- package/build/cjs/mutationObserver.js.map +1 -0
- package/build/cjs/notifyManager.js +114 -0
- package/build/cjs/notifyManager.js.map +1 -0
- package/build/cjs/onlineManager.js +100 -0
- package/build/cjs/onlineManager.js.map +1 -0
- package/build/cjs/queriesObserver.js +170 -0
- package/build/cjs/queriesObserver.js.map +1 -0
- package/build/cjs/query.js +474 -0
- package/build/cjs/query.js.map +1 -0
- package/build/cjs/queryCache.js +140 -0
- package/build/cjs/queryCache.js.map +1 -0
- package/build/cjs/queryClient.js +357 -0
- package/build/cjs/queryClient.js.map +1 -0
- package/build/cjs/queryObserver.js +521 -0
- package/build/cjs/queryObserver.js.map +1 -0
- package/build/cjs/removable.js +47 -0
- package/build/cjs/removable.js.map +1 -0
- package/build/cjs/retryer.js +177 -0
- package/build/cjs/retryer.js.map +1 -0
- package/build/cjs/subscribable.js +43 -0
- package/build/cjs/subscribable.js.map +1 -0
- package/build/cjs/utils.js +356 -0
- package/build/cjs/utils.js.map +1 -0
- package/build/esm/index.js +3077 -0
- package/build/esm/index.js.map +1 -0
- package/build/stats-html.html +2689 -0
- package/build/umd/index.development.js +3106 -0
- package/build/umd/index.development.js.map +1 -0
- package/build/umd/index.production.js +12 -0
- package/build/umd/index.production.js.map +1 -0
- package/package.json +25 -0
- package/src/focusManager.ts +89 -0
- package/src/hydration.ts +164 -0
- package/src/index.ts +35 -0
- package/src/infiniteQueryBehavior.ts +214 -0
- package/src/infiniteQueryObserver.ts +159 -0
- package/src/logger.native.ts +11 -0
- package/src/logger.ts +9 -0
- package/src/mutation.ts +349 -0
- package/src/mutationCache.ts +157 -0
- package/src/mutationObserver.ts +195 -0
- package/src/notifyManager.ts +96 -0
- package/src/onlineManager.ts +89 -0
- package/src/queriesObserver.ts +211 -0
- package/src/query.ts +612 -0
- package/src/queryCache.ts +206 -0
- package/src/queryClient.ts +716 -0
- package/src/queryObserver.ts +748 -0
- package/src/removable.ts +37 -0
- package/src/retryer.ts +215 -0
- package/src/subscribable.ts +33 -0
- package/src/tests/focusManager.test.tsx +155 -0
- package/src/tests/hydration.test.tsx +429 -0
- package/src/tests/infiniteQueryBehavior.test.tsx +124 -0
- package/src/tests/infiniteQueryObserver.test.tsx +64 -0
- package/src/tests/mutationCache.test.tsx +260 -0
- package/src/tests/mutationObserver.test.tsx +75 -0
- package/src/tests/mutations.test.tsx +363 -0
- package/src/tests/notifyManager.test.tsx +51 -0
- package/src/tests/onlineManager.test.tsx +148 -0
- package/src/tests/queriesObserver.test.tsx +330 -0
- package/src/tests/query.test.tsx +888 -0
- package/src/tests/queryCache.test.tsx +236 -0
- package/src/tests/queryClient.test.tsx +1435 -0
- package/src/tests/queryObserver.test.tsx +802 -0
- package/src/tests/utils.test.tsx +360 -0
- package/src/types.ts +705 -0
- package/src/utils.ts +435 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Subscribable } from './subscribable'
|
|
2
|
+
import { isServer } from './utils'
|
|
3
|
+
|
|
4
|
+
type SetupFn = (
|
|
5
|
+
setFocused: (focused?: boolean) => void,
|
|
6
|
+
) => (() => void) | undefined
|
|
7
|
+
|
|
8
|
+
export class FocusManager extends Subscribable {
|
|
9
|
+
private focused?: boolean
|
|
10
|
+
private cleanup?: () => void
|
|
11
|
+
|
|
12
|
+
private setup: SetupFn
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
super()
|
|
16
|
+
this.setup = (onFocus) => {
|
|
17
|
+
// addEventListener does not exist in React Native, but window does
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
19
|
+
if (!isServer && window.addEventListener) {
|
|
20
|
+
const listener = () => onFocus()
|
|
21
|
+
// Listen to visibillitychange and focus
|
|
22
|
+
window.addEventListener('visibilitychange', listener, false)
|
|
23
|
+
window.addEventListener('focus', listener, false)
|
|
24
|
+
|
|
25
|
+
return () => {
|
|
26
|
+
// Be sure to unsubscribe if a new handler is set
|
|
27
|
+
window.removeEventListener('visibilitychange', listener)
|
|
28
|
+
window.removeEventListener('focus', listener)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
protected onSubscribe(): void {
|
|
35
|
+
if (!this.cleanup) {
|
|
36
|
+
this.setEventListener(this.setup)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected onUnsubscribe() {
|
|
41
|
+
if (!this.hasListeners()) {
|
|
42
|
+
this.cleanup?.()
|
|
43
|
+
this.cleanup = undefined
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setEventListener(setup: SetupFn): void {
|
|
48
|
+
this.setup = setup
|
|
49
|
+
this.cleanup?.()
|
|
50
|
+
this.cleanup = setup((focused) => {
|
|
51
|
+
if (typeof focused === 'boolean') {
|
|
52
|
+
this.setFocused(focused)
|
|
53
|
+
} else {
|
|
54
|
+
this.onFocus()
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
setFocused(focused?: boolean): void {
|
|
60
|
+
this.focused = focused
|
|
61
|
+
|
|
62
|
+
if (focused) {
|
|
63
|
+
this.onFocus()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
onFocus(): void {
|
|
68
|
+
this.listeners.forEach((listener) => {
|
|
69
|
+
listener()
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
isFocused(): boolean {
|
|
74
|
+
if (typeof this.focused === 'boolean') {
|
|
75
|
+
return this.focused
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// document global can be unavailable in react native
|
|
79
|
+
if (typeof document === 'undefined') {
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return [undefined, 'visible', 'prerender'].includes(
|
|
84
|
+
document.visibilityState,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const focusManager = new FocusManager()
|
package/src/hydration.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { QueryClient } from './queryClient'
|
|
2
|
+
import type { Query, QueryState } from './query'
|
|
3
|
+
import type {
|
|
4
|
+
MutationKey,
|
|
5
|
+
MutationOptions,
|
|
6
|
+
QueryKey,
|
|
7
|
+
QueryOptions,
|
|
8
|
+
} from './types'
|
|
9
|
+
import type { Mutation, MutationState } from './mutation'
|
|
10
|
+
|
|
11
|
+
// TYPES
|
|
12
|
+
|
|
13
|
+
export interface DehydrateOptions {
|
|
14
|
+
dehydrateMutations?: boolean
|
|
15
|
+
dehydrateQueries?: boolean
|
|
16
|
+
shouldDehydrateMutation?: ShouldDehydrateMutationFunction
|
|
17
|
+
shouldDehydrateQuery?: ShouldDehydrateQueryFunction
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HydrateOptions {
|
|
21
|
+
defaultOptions?: {
|
|
22
|
+
queries?: QueryOptions
|
|
23
|
+
mutations?: MutationOptions
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface DehydratedMutation {
|
|
28
|
+
mutationKey?: MutationKey
|
|
29
|
+
state: MutationState
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface DehydratedQuery {
|
|
33
|
+
queryHash: string
|
|
34
|
+
queryKey: QueryKey
|
|
35
|
+
state: QueryState
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface DehydratedState {
|
|
39
|
+
mutations: DehydratedMutation[]
|
|
40
|
+
queries: DehydratedQuery[]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ShouldDehydrateQueryFunction = (query: Query) => boolean
|
|
44
|
+
|
|
45
|
+
export type ShouldDehydrateMutationFunction = (mutation: Mutation) => boolean
|
|
46
|
+
|
|
47
|
+
// FUNCTIONS
|
|
48
|
+
|
|
49
|
+
function dehydrateMutation(mutation: Mutation): DehydratedMutation {
|
|
50
|
+
return {
|
|
51
|
+
mutationKey: mutation.options.mutationKey,
|
|
52
|
+
state: mutation.state,
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Most config is not dehydrated but instead meant to configure again when
|
|
57
|
+
// consuming the de/rehydrated data, typically with useQuery on the client.
|
|
58
|
+
// Sometimes it might make sense to prefetch data on the server and include
|
|
59
|
+
// in the html-payload, but not consume it on the initial render.
|
|
60
|
+
function dehydrateQuery(query: Query): DehydratedQuery {
|
|
61
|
+
return {
|
|
62
|
+
state: query.state,
|
|
63
|
+
queryKey: query.queryKey,
|
|
64
|
+
queryHash: query.queryHash,
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function defaultShouldDehydrateMutation(mutation: Mutation) {
|
|
69
|
+
return mutation.state.isPaused
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function defaultShouldDehydrateQuery(query: Query) {
|
|
73
|
+
return query.state.status === 'success'
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function dehydrate(
|
|
77
|
+
client: QueryClient,
|
|
78
|
+
options: DehydrateOptions = {},
|
|
79
|
+
): DehydratedState {
|
|
80
|
+
const mutations: DehydratedMutation[] = []
|
|
81
|
+
const queries: DehydratedQuery[] = []
|
|
82
|
+
|
|
83
|
+
if (options.dehydrateMutations !== false) {
|
|
84
|
+
const shouldDehydrateMutation =
|
|
85
|
+
options.shouldDehydrateMutation || defaultShouldDehydrateMutation
|
|
86
|
+
|
|
87
|
+
client
|
|
88
|
+
.getMutationCache()
|
|
89
|
+
.getAll()
|
|
90
|
+
.forEach((mutation) => {
|
|
91
|
+
if (shouldDehydrateMutation(mutation)) {
|
|
92
|
+
mutations.push(dehydrateMutation(mutation))
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (options.dehydrateQueries !== false) {
|
|
98
|
+
const shouldDehydrateQuery =
|
|
99
|
+
options.shouldDehydrateQuery || defaultShouldDehydrateQuery
|
|
100
|
+
|
|
101
|
+
client
|
|
102
|
+
.getQueryCache()
|
|
103
|
+
.getAll()
|
|
104
|
+
.forEach((query) => {
|
|
105
|
+
if (shouldDehydrateQuery(query)) {
|
|
106
|
+
queries.push(dehydrateQuery(query))
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { mutations, queries }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function hydrate(
|
|
115
|
+
client: QueryClient,
|
|
116
|
+
dehydratedState: unknown,
|
|
117
|
+
options?: HydrateOptions,
|
|
118
|
+
): void {
|
|
119
|
+
if (typeof dehydratedState !== 'object' || dehydratedState === null) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const mutationCache = client.getMutationCache()
|
|
124
|
+
const queryCache = client.getQueryCache()
|
|
125
|
+
|
|
126
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
127
|
+
const mutations = (dehydratedState as DehydratedState).mutations || []
|
|
128
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
129
|
+
const queries = (dehydratedState as DehydratedState).queries || []
|
|
130
|
+
|
|
131
|
+
mutations.forEach((dehydratedMutation) => {
|
|
132
|
+
mutationCache.build(
|
|
133
|
+
client,
|
|
134
|
+
{
|
|
135
|
+
...options?.defaultOptions?.mutations,
|
|
136
|
+
mutationKey: dehydratedMutation.mutationKey,
|
|
137
|
+
},
|
|
138
|
+
dehydratedMutation.state,
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
queries.forEach((dehydratedQuery) => {
|
|
143
|
+
const query = queryCache.get(dehydratedQuery.queryHash)
|
|
144
|
+
|
|
145
|
+
// Do not hydrate if an existing query exists with newer data
|
|
146
|
+
if (query) {
|
|
147
|
+
if (query.state.dataUpdatedAt < dehydratedQuery.state.dataUpdatedAt) {
|
|
148
|
+
query.setState(dehydratedQuery.state)
|
|
149
|
+
}
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Restore query
|
|
154
|
+
queryCache.build(
|
|
155
|
+
client,
|
|
156
|
+
{
|
|
157
|
+
...options?.defaultOptions?.queries,
|
|
158
|
+
queryKey: dehydratedQuery.queryKey,
|
|
159
|
+
queryHash: dehydratedQuery.queryHash,
|
|
160
|
+
},
|
|
161
|
+
dehydratedQuery.state,
|
|
162
|
+
)
|
|
163
|
+
})
|
|
164
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export { CancelledError } from './retryer'
|
|
2
|
+
export { QueryCache } from './queryCache'
|
|
3
|
+
export { QueryClient } from './queryClient'
|
|
4
|
+
export { QueryObserver } from './queryObserver'
|
|
5
|
+
export { QueriesObserver } from './queriesObserver'
|
|
6
|
+
export { InfiniteQueryObserver } from './infiniteQueryObserver'
|
|
7
|
+
export { MutationCache } from './mutationCache'
|
|
8
|
+
export { MutationObserver } from './mutationObserver'
|
|
9
|
+
export { notifyManager } from './notifyManager'
|
|
10
|
+
export { focusManager } from './focusManager'
|
|
11
|
+
export { onlineManager } from './onlineManager'
|
|
12
|
+
export {
|
|
13
|
+
hashQueryKey,
|
|
14
|
+
isError,
|
|
15
|
+
parseQueryArgs,
|
|
16
|
+
parseFilterArgs,
|
|
17
|
+
parseMutationFilterArgs,
|
|
18
|
+
parseMutationArgs,
|
|
19
|
+
} from './utils'
|
|
20
|
+
export type { MutationFilters, QueryFilters } from './utils'
|
|
21
|
+
export { isCancelledError } from './retryer'
|
|
22
|
+
export { dehydrate, hydrate } from './hydration'
|
|
23
|
+
|
|
24
|
+
// Types
|
|
25
|
+
export * from './types'
|
|
26
|
+
export type { Query } from './query'
|
|
27
|
+
export type { Mutation } from './mutation'
|
|
28
|
+
export type { Logger } from './logger'
|
|
29
|
+
export type {
|
|
30
|
+
DehydrateOptions,
|
|
31
|
+
DehydratedState,
|
|
32
|
+
HydrateOptions,
|
|
33
|
+
ShouldDehydrateMutationFunction,
|
|
34
|
+
ShouldDehydrateQueryFunction,
|
|
35
|
+
} from './hydration'
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { QueryBehavior } from './query'
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
InfiniteData,
|
|
5
|
+
QueryFunctionContext,
|
|
6
|
+
QueryOptions,
|
|
7
|
+
RefetchQueryFilters,
|
|
8
|
+
} from './types'
|
|
9
|
+
|
|
10
|
+
export function infiniteQueryBehavior<
|
|
11
|
+
TQueryFnData,
|
|
12
|
+
TError,
|
|
13
|
+
TData,
|
|
14
|
+
>(): QueryBehavior<TQueryFnData, TError, InfiniteData<TData>> {
|
|
15
|
+
return {
|
|
16
|
+
onFetch: (context) => {
|
|
17
|
+
context.fetchFn = () => {
|
|
18
|
+
const refetchPage: RefetchQueryFilters['refetchPage'] | undefined =
|
|
19
|
+
context.fetchOptions?.meta?.refetchPage
|
|
20
|
+
const fetchMore = context.fetchOptions?.meta?.fetchMore
|
|
21
|
+
const pageParam = fetchMore?.pageParam
|
|
22
|
+
const isFetchingNextPage = fetchMore?.direction === 'forward'
|
|
23
|
+
const isFetchingPreviousPage = fetchMore?.direction === 'backward'
|
|
24
|
+
const oldPages = context.state.data?.pages || []
|
|
25
|
+
const oldPageParams = context.state.data?.pageParams || []
|
|
26
|
+
let newPageParams = oldPageParams
|
|
27
|
+
let cancelled = false
|
|
28
|
+
|
|
29
|
+
const addSignalProperty = (object: unknown) => {
|
|
30
|
+
Object.defineProperty(object, 'signal', {
|
|
31
|
+
enumerable: true,
|
|
32
|
+
get: () => {
|
|
33
|
+
if (context.signal?.aborted) {
|
|
34
|
+
cancelled = true
|
|
35
|
+
} else {
|
|
36
|
+
context.signal?.addEventListener('abort', () => {
|
|
37
|
+
cancelled = true
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
return context.signal
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get query function
|
|
46
|
+
const queryFn =
|
|
47
|
+
context.options.queryFn || (() => Promise.reject('Missing queryFn'))
|
|
48
|
+
|
|
49
|
+
const buildNewPages = (
|
|
50
|
+
pages: unknown[],
|
|
51
|
+
param: unknown,
|
|
52
|
+
page: unknown,
|
|
53
|
+
previous?: boolean,
|
|
54
|
+
) => {
|
|
55
|
+
newPageParams = previous
|
|
56
|
+
? [param, ...newPageParams]
|
|
57
|
+
: [...newPageParams, param]
|
|
58
|
+
return previous ? [page, ...pages] : [...pages, page]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Create function to fetch a page
|
|
62
|
+
const fetchPage = (
|
|
63
|
+
pages: unknown[],
|
|
64
|
+
manual?: boolean,
|
|
65
|
+
param?: unknown,
|
|
66
|
+
previous?: boolean,
|
|
67
|
+
): Promise<unknown[]> => {
|
|
68
|
+
if (cancelled) {
|
|
69
|
+
return Promise.reject('Cancelled')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof param === 'undefined' && !manual && pages.length) {
|
|
73
|
+
return Promise.resolve(pages)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const queryFnContext: QueryFunctionContext = {
|
|
77
|
+
queryKey: context.queryKey,
|
|
78
|
+
pageParam: param,
|
|
79
|
+
meta: context.meta,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
addSignalProperty(queryFnContext)
|
|
83
|
+
|
|
84
|
+
const queryFnResult = queryFn(queryFnContext)
|
|
85
|
+
|
|
86
|
+
const promise = Promise.resolve(queryFnResult).then((page) =>
|
|
87
|
+
buildNewPages(pages, param, page, previous),
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return promise
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let promise: Promise<unknown[]>
|
|
94
|
+
|
|
95
|
+
// Fetch first page?
|
|
96
|
+
if (!oldPages.length) {
|
|
97
|
+
promise = fetchPage([])
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Fetch next page?
|
|
101
|
+
else if (isFetchingNextPage) {
|
|
102
|
+
const manual = typeof pageParam !== 'undefined'
|
|
103
|
+
const param = manual
|
|
104
|
+
? pageParam
|
|
105
|
+
: getNextPageParam(context.options, oldPages)
|
|
106
|
+
promise = fetchPage(oldPages, manual, param)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fetch previous page?
|
|
110
|
+
else if (isFetchingPreviousPage) {
|
|
111
|
+
const manual = typeof pageParam !== 'undefined'
|
|
112
|
+
const param = manual
|
|
113
|
+
? pageParam
|
|
114
|
+
: getPreviousPageParam(context.options, oldPages)
|
|
115
|
+
promise = fetchPage(oldPages, manual, param, true)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Refetch pages
|
|
119
|
+
else {
|
|
120
|
+
newPageParams = []
|
|
121
|
+
|
|
122
|
+
const manual = typeof context.options.getNextPageParam === 'undefined'
|
|
123
|
+
|
|
124
|
+
const shouldFetchFirstPage =
|
|
125
|
+
refetchPage && oldPages[0]
|
|
126
|
+
? refetchPage(oldPages[0], 0, oldPages)
|
|
127
|
+
: true
|
|
128
|
+
|
|
129
|
+
// Fetch first page
|
|
130
|
+
promise = shouldFetchFirstPage
|
|
131
|
+
? fetchPage([], manual, oldPageParams[0])
|
|
132
|
+
: Promise.resolve(buildNewPages([], oldPageParams[0], oldPages[0]))
|
|
133
|
+
|
|
134
|
+
// Fetch remaining pages
|
|
135
|
+
for (let i = 1; i < oldPages.length; i++) {
|
|
136
|
+
promise = promise.then((pages) => {
|
|
137
|
+
const shouldFetchNextPage =
|
|
138
|
+
refetchPage && oldPages[i]
|
|
139
|
+
? refetchPage(oldPages[i], i, oldPages)
|
|
140
|
+
: true
|
|
141
|
+
|
|
142
|
+
if (shouldFetchNextPage) {
|
|
143
|
+
const param = manual
|
|
144
|
+
? oldPageParams[i]
|
|
145
|
+
: getNextPageParam(context.options, pages)
|
|
146
|
+
return fetchPage(pages, manual, param)
|
|
147
|
+
}
|
|
148
|
+
return Promise.resolve(
|
|
149
|
+
buildNewPages(pages, oldPageParams[i], oldPages[i]),
|
|
150
|
+
)
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const finalPromise = promise.then((pages) => ({
|
|
156
|
+
pages,
|
|
157
|
+
pageParams: newPageParams,
|
|
158
|
+
}))
|
|
159
|
+
|
|
160
|
+
return finalPromise
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function getNextPageParam(
|
|
167
|
+
options: QueryOptions<any, any>,
|
|
168
|
+
pages: unknown[],
|
|
169
|
+
): unknown | undefined {
|
|
170
|
+
return options.getNextPageParam?.(pages[pages.length - 1], pages)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function getPreviousPageParam(
|
|
174
|
+
options: QueryOptions<any, any>,
|
|
175
|
+
pages: unknown[],
|
|
176
|
+
): unknown | undefined {
|
|
177
|
+
return options.getPreviousPageParam?.(pages[0], pages)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Checks if there is a next page.
|
|
182
|
+
* Returns `undefined` if it cannot be determined.
|
|
183
|
+
*/
|
|
184
|
+
export function hasNextPage(
|
|
185
|
+
options: QueryOptions<any, any, any, any>,
|
|
186
|
+
pages?: unknown,
|
|
187
|
+
): boolean | undefined {
|
|
188
|
+
if (options.getNextPageParam && Array.isArray(pages)) {
|
|
189
|
+
const nextPageParam = getNextPageParam(options, pages)
|
|
190
|
+
return (
|
|
191
|
+
typeof nextPageParam !== 'undefined' &&
|
|
192
|
+
nextPageParam !== null &&
|
|
193
|
+
nextPageParam !== false
|
|
194
|
+
)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Checks if there is a previous page.
|
|
200
|
+
* Returns `undefined` if it cannot be determined.
|
|
201
|
+
*/
|
|
202
|
+
export function hasPreviousPage(
|
|
203
|
+
options: QueryOptions<any, any, any, any>,
|
|
204
|
+
pages?: unknown,
|
|
205
|
+
): boolean | undefined {
|
|
206
|
+
if (options.getPreviousPageParam && Array.isArray(pages)) {
|
|
207
|
+
const previousPageParam = getPreviousPageParam(options, pages)
|
|
208
|
+
return (
|
|
209
|
+
typeof previousPageParam !== 'undefined' &&
|
|
210
|
+
previousPageParam !== null &&
|
|
211
|
+
previousPageParam !== false
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DefaultedInfiniteQueryObserverOptions,
|
|
3
|
+
FetchNextPageOptions,
|
|
4
|
+
FetchPreviousPageOptions,
|
|
5
|
+
InfiniteData,
|
|
6
|
+
InfiniteQueryObserverOptions,
|
|
7
|
+
InfiniteQueryObserverResult,
|
|
8
|
+
QueryKey,
|
|
9
|
+
} from './types'
|
|
10
|
+
import type { QueryClient } from './queryClient'
|
|
11
|
+
import {
|
|
12
|
+
NotifyOptions,
|
|
13
|
+
ObserverFetchOptions,
|
|
14
|
+
QueryObserver,
|
|
15
|
+
} from './queryObserver'
|
|
16
|
+
import {
|
|
17
|
+
hasNextPage,
|
|
18
|
+
hasPreviousPage,
|
|
19
|
+
infiniteQueryBehavior,
|
|
20
|
+
} from './infiniteQueryBehavior'
|
|
21
|
+
import { Query } from './query'
|
|
22
|
+
|
|
23
|
+
type InfiniteQueryObserverListener<TData, TError> = (
|
|
24
|
+
result: InfiniteQueryObserverResult<TData, TError>,
|
|
25
|
+
) => void
|
|
26
|
+
|
|
27
|
+
export class InfiniteQueryObserver<
|
|
28
|
+
TQueryFnData = unknown,
|
|
29
|
+
TError = unknown,
|
|
30
|
+
TData = TQueryFnData,
|
|
31
|
+
TQueryData = TQueryFnData,
|
|
32
|
+
TQueryKey extends QueryKey = QueryKey,
|
|
33
|
+
> extends QueryObserver<
|
|
34
|
+
TQueryFnData,
|
|
35
|
+
TError,
|
|
36
|
+
InfiniteData<TData>,
|
|
37
|
+
InfiniteData<TQueryData>,
|
|
38
|
+
TQueryKey
|
|
39
|
+
> {
|
|
40
|
+
// Type override
|
|
41
|
+
subscribe!: (
|
|
42
|
+
listener?: InfiniteQueryObserverListener<TData, TError>,
|
|
43
|
+
) => () => void
|
|
44
|
+
|
|
45
|
+
// Type override
|
|
46
|
+
getCurrentResult!: () => InfiniteQueryObserverResult<TData, TError>
|
|
47
|
+
|
|
48
|
+
// Type override
|
|
49
|
+
protected fetch!: (
|
|
50
|
+
fetchOptions: ObserverFetchOptions,
|
|
51
|
+
) => Promise<InfiniteQueryObserverResult<TData, TError>>
|
|
52
|
+
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
54
|
+
constructor(
|
|
55
|
+
client: QueryClient,
|
|
56
|
+
options: InfiniteQueryObserverOptions<
|
|
57
|
+
TQueryFnData,
|
|
58
|
+
TError,
|
|
59
|
+
TData,
|
|
60
|
+
TQueryData,
|
|
61
|
+
TQueryKey
|
|
62
|
+
>,
|
|
63
|
+
) {
|
|
64
|
+
super(client, options)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
protected bindMethods(): void {
|
|
68
|
+
super.bindMethods()
|
|
69
|
+
this.fetchNextPage = this.fetchNextPage.bind(this)
|
|
70
|
+
this.fetchPreviousPage = this.fetchPreviousPage.bind(this)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
setOptions(
|
|
74
|
+
options?: InfiniteQueryObserverOptions<
|
|
75
|
+
TQueryFnData,
|
|
76
|
+
TError,
|
|
77
|
+
TData,
|
|
78
|
+
TQueryData,
|
|
79
|
+
TQueryKey
|
|
80
|
+
>,
|
|
81
|
+
notifyOptions?: NotifyOptions,
|
|
82
|
+
): void {
|
|
83
|
+
super.setOptions(
|
|
84
|
+
{
|
|
85
|
+
...options,
|
|
86
|
+
behavior: infiniteQueryBehavior(),
|
|
87
|
+
},
|
|
88
|
+
notifyOptions,
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
getOptimisticResult(
|
|
93
|
+
options: DefaultedInfiniteQueryObserverOptions<
|
|
94
|
+
TQueryFnData,
|
|
95
|
+
TError,
|
|
96
|
+
TData,
|
|
97
|
+
TQueryData,
|
|
98
|
+
TQueryKey
|
|
99
|
+
>,
|
|
100
|
+
): InfiniteQueryObserverResult<TData, TError> {
|
|
101
|
+
options.behavior = infiniteQueryBehavior()
|
|
102
|
+
return super.getOptimisticResult(options) as InfiniteQueryObserverResult<
|
|
103
|
+
TData,
|
|
104
|
+
TError
|
|
105
|
+
>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fetchNextPage({ pageParam, ...options }: FetchNextPageOptions = {}): Promise<
|
|
109
|
+
InfiniteQueryObserverResult<TData, TError>
|
|
110
|
+
> {
|
|
111
|
+
return this.fetch({
|
|
112
|
+
...options,
|
|
113
|
+
meta: {
|
|
114
|
+
fetchMore: { direction: 'forward', pageParam },
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fetchPreviousPage({
|
|
120
|
+
pageParam,
|
|
121
|
+
...options
|
|
122
|
+
}: FetchPreviousPageOptions = {}): Promise<
|
|
123
|
+
InfiniteQueryObserverResult<TData, TError>
|
|
124
|
+
> {
|
|
125
|
+
return this.fetch({
|
|
126
|
+
...options,
|
|
127
|
+
meta: {
|
|
128
|
+
fetchMore: { direction: 'backward', pageParam },
|
|
129
|
+
},
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
protected createResult(
|
|
134
|
+
query: Query<TQueryFnData, TError, InfiniteData<TQueryData>, TQueryKey>,
|
|
135
|
+
options: InfiniteQueryObserverOptions<
|
|
136
|
+
TQueryFnData,
|
|
137
|
+
TError,
|
|
138
|
+
TData,
|
|
139
|
+
TQueryData,
|
|
140
|
+
TQueryKey
|
|
141
|
+
>,
|
|
142
|
+
): InfiniteQueryObserverResult<TData, TError> {
|
|
143
|
+
const { state } = query
|
|
144
|
+
const result = super.createResult(query, options)
|
|
145
|
+
return {
|
|
146
|
+
...result,
|
|
147
|
+
fetchNextPage: this.fetchNextPage,
|
|
148
|
+
fetchPreviousPage: this.fetchPreviousPage,
|
|
149
|
+
hasNextPage: hasNextPage(options, state.data?.pages),
|
|
150
|
+
hasPreviousPage: hasPreviousPage(options, state.data?.pages),
|
|
151
|
+
isFetchingNextPage:
|
|
152
|
+
state.fetchStatus === 'fetching' &&
|
|
153
|
+
state.fetchMeta?.fetchMore?.direction === 'forward',
|
|
154
|
+
isFetchingPreviousPage:
|
|
155
|
+
state.fetchStatus === 'fetching' &&
|
|
156
|
+
state.fetchMeta?.fetchMore?.direction === 'backward',
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Logger } from './logger'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* See https://github.com/tannerlinsley/react-query/issues/795
|
|
5
|
+
* and https://github.com/tannerlinsley/react-query/pull/3246/#discussion_r795105707
|
|
6
|
+
*/
|
|
7
|
+
export const defaultLogger: Logger = {
|
|
8
|
+
log: console.log,
|
|
9
|
+
warn: console.warn,
|
|
10
|
+
error: console.warn,
|
|
11
|
+
}
|