Skip to content

Commit 7f6d50c

Browse files
feat: concurrent request handle with dataloader (#392)
* feat: concurrent request handle * test: update testing
1 parent 1af31ac commit 7f6d50c

File tree

10 files changed

+860
-477
lines changed

10 files changed

+860
-477
lines changed

packages/use-dataloader/src/DataLoaderProvider.tsx

Lines changed: 150 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -7,87 +7,158 @@ import React, {
77
useContext,
88
useMemo,
99
useRef,
10+
useState,
1011
} from 'react'
12+
import { KEY_IS_NOT_STRING_ERROR, StatusEnum } from './constants'
13+
import DataLoader from './dataloader'
14+
import { OnErrorFn, PromiseType } from './types'
15+
16+
type RequestQueue = Record<string, DataLoader>
17+
type CachedData = Record<string, unknown>
18+
type Reloads = Record<string, () => Promise<void | unknown>>
19+
20+
type UseDataLoaderInitializerArgs<T = unknown> = {
21+
enabled?: boolean
22+
key: string
23+
status?: StatusEnum
24+
method: () => PromiseType<T>
25+
pollingInterval?: number
26+
keepPreviousData?: boolean
27+
}
1128

1229
interface Context {
13-
addCachedData: (key: string, newData: unknown) => void;
14-
addReload: (key: string, method: () => Promise<void>) => void;
15-
cacheKeyPrefix: string;
30+
addCachedData: (key: string, newData: unknown) => void
31+
addReload: (key: string, method: () => Promise<void | unknown>) => void
32+
addRequest: (key: string, args: UseDataLoaderInitializerArgs) => DataLoader
33+
cacheKeyPrefix: string
1634
onError?: (error: Error) => void | Promise<void>
17-
clearAllCachedData: () => void;
18-
clearAllReloads: () => void;
19-
clearCachedData: (key?: string | undefined) => void;
20-
clearReload: (key?: string | undefined) => void;
21-
getCachedData: (key?: string | undefined) => unknown;
22-
getReloads: (key?: string | undefined) => (() => Promise<void>) | Reloads;
23-
reload: (key?: string | undefined) => Promise<void>;
24-
reloadAll: () => Promise<void>;
35+
clearAllCachedData: () => void
36+
clearAllReloads: () => void
37+
clearCachedData: (key?: string) => void
38+
clearReload: (key?: string) => void
39+
getCachedData: (key?: string) => unknown | CachedData
40+
getReloads: (key?: string) => (() => Promise<void | unknown>) | Reloads
41+
getRequest: (key: string) => DataLoader | undefined
42+
reload: (key?: string) => Promise<void>
43+
reloadAll: () => Promise<void>
2544
}
2645

27-
type CachedData = Record<string, unknown>
28-
type Reloads = Record<string, () => Promise<void>>
29-
3046
// @ts-expect-error we force the context to undefined, should be corrected with default values
3147
export const DataLoaderContext = createContext<Context>(undefined)
3248

33-
const DataLoaderProvider = ({ children, cacheKeyPrefix, onError }: {
34-
children: ReactNode, cacheKeyPrefix: string, onError: (error: Error) => void | Promise<void>
49+
const DataLoaderProvider = ({
50+
children,
51+
cacheKeyPrefix,
52+
onError,
53+
}: {
54+
children: ReactNode
55+
cacheKeyPrefix: string
56+
onError: OnErrorFn
3557
}): ReactElement => {
36-
const cachedData = useRef<CachedData>({})
58+
const [requestQueue, setRequestQueue] = useState({} as RequestQueue)
59+
const [cachedData, setCachedDataPrivate] = useState<CachedData>({})
3760
const reloads = useRef<Reloads>({})
3861

39-
const setCachedData = useCallback((compute: CachedData | ((data: CachedData) => CachedData)) => {
40-
if (typeof compute === 'function') {
41-
cachedData.current = compute(cachedData.current)
42-
} else {
43-
cachedData.current = compute
44-
}
45-
}, [])
62+
const computeKey = useCallback(
63+
(key: string) => `${cacheKeyPrefix ? `${cacheKeyPrefix}-` : ''}${key}`,
64+
[cacheKeyPrefix],
65+
)
4666

47-
const setReloads = useCallback((compute: Reloads | ((data: Reloads) => Reloads)) => {
48-
if (typeof compute === 'function') {
49-
reloads.current = compute(reloads.current)
50-
} else {
51-
reloads.current = compute
52-
}
53-
}, [])
67+
const setCachedData = useCallback(
68+
(compute: CachedData | ((data: CachedData) => CachedData)) => {
69+
if (typeof compute === 'function') {
70+
setCachedDataPrivate(current => compute(current))
71+
} else {
72+
setCachedDataPrivate(compute)
73+
}
74+
},
75+
[],
76+
)
77+
78+
const setReloads = useCallback(
79+
(compute: Reloads | ((data: Reloads) => Reloads)) => {
80+
if (typeof compute === 'function') {
81+
reloads.current = compute(reloads.current)
82+
} else {
83+
reloads.current = compute
84+
}
85+
},
86+
[],
87+
)
5488

5589
const addCachedData = useCallback(
5690
(key: string, newData: unknown) => {
57-
if (key && typeof key === 'string' && newData) {
58-
setCachedData(actualCachedData => ({
59-
...actualCachedData,
60-
[key]: newData,
61-
}))
91+
if (newData) {
92+
if (key && typeof key === 'string') {
93+
setCachedData(actualCachedData => ({
94+
...actualCachedData,
95+
[computeKey(key)]: newData,
96+
}))
97+
} else throw new Error(KEY_IS_NOT_STRING_ERROR)
6298
}
6399
},
64-
[setCachedData],
100+
[setCachedData, computeKey],
65101
)
66102

67103
const addReload = useCallback(
68-
(key: string, method: () => Promise<void>) => {
69-
if (key && typeof key === 'string' && method) {
70-
setReloads(actualReloads => ({
71-
...actualReloads,
72-
[key]: method,
104+
(key: string, method: () => Promise<void | unknown>) => {
105+
if (method) {
106+
if (key && typeof key === 'string') {
107+
setReloads(actualReloads => ({
108+
...actualReloads,
109+
[computeKey(key)]: method,
110+
}))
111+
} else throw new Error(KEY_IS_NOT_STRING_ERROR)
112+
}
113+
},
114+
[setReloads, computeKey],
115+
)
116+
117+
const addRequest = useCallback(
118+
(key: string, args: UseDataLoaderInitializerArgs) => {
119+
if (key && typeof key === 'string') {
120+
const notifyChanges = (updatedRequest: DataLoader) => {
121+
setRequestQueue(current => ({
122+
...current,
123+
[computeKey(updatedRequest.key)]: updatedRequest,
124+
}))
125+
}
126+
const newRequest = new DataLoader({ ...args, notify: notifyChanges })
127+
newRequest.addOnSuccessListener(result => {
128+
if (result !== undefined && result !== null)
129+
addCachedData(key, result)
130+
})
131+
setRequestQueue(current => ({
132+
...current,
133+
[computeKey(key)]: newRequest,
73134
}))
135+
136+
addReload(key, newRequest.launch)
137+
138+
return newRequest
74139
}
140+
throw new Error(KEY_IS_NOT_STRING_ERROR)
75141
},
76-
[setReloads],
142+
[computeKey, addCachedData, addReload],
143+
)
144+
145+
const getRequest = useCallback(
146+
(key: string) => requestQueue[computeKey(key)],
147+
[computeKey, requestQueue],
77148
)
78149

79150
const clearReload = useCallback(
80151
(key?: string) => {
81152
if (key && typeof key === 'string') {
82153
setReloads(actualReloads => {
83154
const tmp = actualReloads
84-
delete tmp[key]
155+
delete tmp[computeKey(key)]
85156

86157
return tmp
87158
})
88-
}
159+
} else throw new Error(KEY_IS_NOT_STRING_ERROR)
89160
},
90-
[setReloads],
161+
[setReloads, computeKey],
91162
)
92163

93164
const clearAllReloads = useCallback(() => {
@@ -99,70 +170,83 @@ const DataLoaderProvider = ({ children, cacheKeyPrefix, onError }: {
99170
if (key && typeof key === 'string') {
100171
setCachedData(actualCachedData => {
101172
const tmp = actualCachedData
102-
delete tmp[key]
173+
delete tmp[computeKey(key)]
103174

104175
return tmp
105176
})
106-
}
177+
} else throw new Error(KEY_IS_NOT_STRING_ERROR)
107178
},
108-
[setCachedData],
179+
[setCachedData, computeKey],
109180
)
110181
const clearAllCachedData = useCallback(() => {
111182
setCachedData({})
112183
}, [setCachedData])
113184

114-
const reload = useCallback(async (key?: string) => {
115-
if (key && typeof key === 'string') {
116-
await (reloads.current[key] && reloads.current[key]())
117-
}
118-
}, [])
185+
const reload = useCallback(
186+
async (key?: string) => {
187+
if (key && typeof key === 'string') {
188+
await reloads.current[computeKey(key)]?.()
189+
} else throw new Error(KEY_IS_NOT_STRING_ERROR)
190+
},
191+
[computeKey],
192+
)
119193

120194
const reloadAll = useCallback(async () => {
121195
await Promise.all(
122196
Object.values(reloads.current).map(reloadFn => reloadFn()),
123197
)
124198
}, [])
125199

126-
const getCachedData = useCallback((key?: string) => {
127-
if (key) {
128-
return cachedData.current[key] || undefined
129-
}
200+
const getCachedData = useCallback(
201+
(key?: string) => {
202+
if (key) {
203+
return cachedData[computeKey(key)]
204+
}
130205

131-
return cachedData.current
132-
}, [])
206+
return cachedData
207+
},
208+
[computeKey, cachedData],
209+
)
133210

134-
const getReloads = useCallback((key?: string) => {
135-
if (key) {
136-
return reloads.current[key] || undefined
137-
}
211+
const getReloads = useCallback(
212+
(key?: string) => {
213+
if (key) {
214+
return reloads.current[computeKey(key)]
215+
}
138216

139-
return reloads.current
140-
}, [])
217+
return reloads.current
218+
},
219+
[computeKey],
220+
)
141221

142222
const value = useMemo(
143223
() => ({
144224
addCachedData,
145225
addReload,
226+
addRequest,
146227
cacheKeyPrefix,
147228
clearAllCachedData,
148229
clearAllReloads,
149230
clearCachedData,
150231
clearReload,
151232
getCachedData,
152233
getReloads,
234+
getRequest,
153235
onError,
154236
reload,
155237
reloadAll,
156238
}),
157239
[
158240
addCachedData,
159241
addReload,
242+
addRequest,
160243
cacheKeyPrefix,
161244
clearAllCachedData,
162245
clearAllReloads,
163246
clearCachedData,
164247
clearReload,
165248
getCachedData,
249+
getRequest,
166250
getReloads,
167251
onError,
168252
reload,

0 commit comments

Comments
 (0)