-
Notifications
You must be signed in to change notification settings - Fork 13
/
useDataQuery.ts
179 lines (155 loc) · 5.06 KB
/
useDataQuery.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
import { useState, useRef, useCallback, useDebugValue } from 'react'
import { useQuery, setLogger } from 'react-query'
import type {
Query,
QueryOptions,
QueryResult,
QueryVariables,
} from '../../engine'
import type { FetchError } from '../../engine/types/FetchError'
import type { QueryRenderInput, QueryRefetchFunction } from '../../types'
import { mergeAndCompareVariables } from './mergeAndCompareVariables'
import { useDataEngine } from './useDataEngine'
import { useStaticInput } from './useStaticInput'
const noop = () => {
/**
* Used to silence the default react-query logger. Eventually we
* could expose the setLogger functionality and remove the call
* to setLogger here.
*/
}
setLogger({
log: noop,
warn: noop,
error: noop,
})
type QueryState = {
enabled: boolean
variables?: QueryVariables
variablesHash?: string
refetchCallback?: (data: any) => void
}
export const useDataQuery = <TQueryResult = QueryResult>(
query: Query,
{
onComplete: userOnSuccess,
onError: userOnError,
variables: initialVariables = {},
lazy: initialLazy = false,
}: QueryOptions<TQueryResult> = {}
): QueryRenderInput<TQueryResult> => {
const [staticQuery] = useStaticInput<Query>(query, {
warn: true,
name: 'query',
})
const [variablesUpdateCount, setVariablesUpdateCount] = useState(0)
const queryState = useRef<QueryState>({
variables: initialVariables,
variablesHash: undefined,
enabled: !initialLazy,
refetchCallback: undefined,
})
/**
* Display current query state and refetch count in React DevTools
*/
useDebugValue(
{
variablesUpdateCount,
enabled: queryState.current.enabled,
variables: queryState.current.variables,
},
(debugValue) => JSON.stringify(debugValue)
)
/**
* User callbacks and refetch handling
*/
const onSuccess = (data: any) => {
queryState.current.refetchCallback?.(data)
queryState.current.refetchCallback = undefined
if (userOnSuccess) {
userOnSuccess(data)
}
}
const onError = (error: FetchError) => {
// If we'd want to reject on errors we'd call the cb with the error here
queryState.current.refetchCallback = undefined
if (userOnError) {
userOnError(error)
}
}
/**
* Setting up react-query
*/
const engine = useDataEngine()
const queryKey = [staticQuery, queryState.current.variables]
const queryFn = () =>
engine.query(staticQuery, { variables: queryState.current.variables })
const {
isIdle,
isFetching,
isLoading,
error,
data,
refetch: queryRefetch,
} = useQuery(queryKey, queryFn, {
enabled: queryState.current.enabled,
onSuccess,
onError,
})
/**
* Refetch allows a user to update the variables or just
* trigger a refetch of the query with the current variables.
*
* We're using useCallback to make the identity of the function
* as stable as possible, so that it won't trigger excessive
* rerenders when used for side-effects.
*/
const refetch: QueryRefetchFunction = useCallback(
(newVariables) => {
const { identical, mergedVariables, mergedVariablesHash } =
mergeAndCompareVariables(
queryState.current.variables,
newVariables,
queryState.current.variablesHash
)
/**
* If there are no updates that will trigger an automatic refetch
* we'll need to call react-query's refetch directly
*/
if (queryState.current.enabled && identical) {
return queryRefetch({
cancelRefetch: true,
throwOnError: false,
}).then(({ data }) => data)
}
queryState.current.variables = mergedVariables
queryState.current.variablesHash = mergedVariablesHash
queryState.current.enabled = true
// This promise does not currently reject on errors
const refetchPromise = new Promise((resolve) => {
queryState.current.refetchCallback = (data) => {
resolve(data)
}
})
// Trigger a react-query refetch by incrementing variablesUpdateCount state
setVariablesUpdateCount((prevCount) => prevCount + 1)
return refetchPromise
},
[queryRefetch]
)
/**
* react-query returns null or an error, but we return undefined
* or an error, so this ensures consistency with the other types.
*/
const ourError = error || undefined
return {
engine,
// A query is idle if it is lazy and no initial data is available.
called: !isIdle,
loading: isLoading,
fetching: isFetching,
error: ourError,
data,
refetch,
}
}