@choksheak/react-utils 0.2.3 → 0.2.4
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/README.md +3 -1
- package/package.json +7 -2
- package/sharedQuery.d.mts +38 -7
- package/sharedQuery.d.ts +37 -6
- package/sharedQuery.js +149 -89
- package/sharedQuery.mjs +147 -87
- package/src/sharedQuery.ts +198 -117
- package/src/utils/useDeepMemo.ts +19 -0
- package/utils/useDeepMemo.d.mts +4 -0
- package/utils/useDeepMemo.d.ts +2 -0
- package/utils/useDeepMemo.js +51 -0
- package/utils/useDeepMemo.mjs +16 -0
- package/docs/.nojekyll +0 -1
- package/docs/assets/hierarchy.js +0 -1
- package/docs/assets/highlight.css +0 -71
- package/docs/assets/icons.js +0 -18
- package/docs/assets/icons.svg +0 -1
- package/docs/assets/main.js +0 -60
- package/docs/assets/navigation.js +0 -1
- package/docs/assets/search.js +0 -1
- package/docs/assets/style.css +0 -1633
- package/docs/classes/sharedQuery.SharedQuery.html +0 -22
- package/docs/classes/sharedState.SharedState.html +0 -9
- package/docs/classes/utils_storage.InMemoryStorageAdapter.html +0 -9
- package/docs/classes/utils_storage.IndexedDbAdapter.html +0 -9
- package/docs/classes/utils_storage.LocalStorageAdapter.html +0 -9
- package/docs/functions/sharedQuery.sharedQuery.html +0 -9
- package/docs/functions/sharedQuery.useSharedQuery.html +0 -6
- package/docs/functions/sharedState.sharedState.html +0 -3
- package/docs/functions/sharedState.useSharedState.html +0 -3
- package/docs/functions/sharedState.useSharedStateSetter.html +0 -2
- package/docs/functions/sharedState.useSharedStateValue.html +0 -2
- package/docs/functions/utils_storage.getStorageAdapter.html +0 -1
- package/docs/functions/utils_stringify.stringifyDeterministicForKeys.html +0 -7
- package/docs/hierarchy.html +0 -1
- package/docs/index.html +0 -2
- package/docs/modules/fetcher.html +0 -1
- package/docs/modules/index.html +0 -1
- package/docs/modules/sharedQuery.html +0 -1
- package/docs/modules/sharedState.html +0 -1
- package/docs/modules/utils_storage.html +0 -1
- package/docs/modules/utils_stringify.html +0 -1
- package/docs/modules.html +0 -1
- package/docs/types/sharedQuery.FetchFn.html +0 -1
- package/docs/types/sharedQuery.PersistTo.html +0 -1
- package/docs/types/sharedQuery.QueryStateValue.html +0 -9
- package/docs/types/sharedQuery.SharedQueryOptions.html +0 -1
- package/docs/types/sharedQuery.SharedQueryState.html +0 -3
- package/docs/types/sharedQuery.UseQueryResult.html +0 -1
- package/docs/types/sharedState.SharedStateOptions.html +0 -1
- package/docs/types/utils_storage.StorageAdapter.html +0 -5
- package/docs/types/utils_storage.StorageOptions.html +0 -5
- package/docs/variables/fetcher.fetcher.html +0 -2
- package/docs/variables/fetcher.fetcherWithRetries.html +0 -2
- package/docs/variables/sharedQuery.SharedQueryDefaults.html +0 -2
- package/docs/variables/sharedState.SharedStateDefaults.html +0 -6
package/src/sharedQuery.ts
CHANGED
|
@@ -9,8 +9,9 @@ import {
|
|
|
9
9
|
useSharedState,
|
|
10
10
|
} from "./sharedState";
|
|
11
11
|
import { stringifyDeterministicForKeys } from "./utils/stringify";
|
|
12
|
+
import { useDeepMemo } from "./utils/useDeepMemo";
|
|
12
13
|
|
|
13
|
-
export type
|
|
14
|
+
export type QueryFn<TArgs extends unknown[], TData> = (
|
|
14
15
|
...args: TArgs
|
|
15
16
|
) => Promise<TData> | TData;
|
|
16
17
|
|
|
@@ -24,13 +25,34 @@ export type QueryStateValue<TData> = {
|
|
|
24
25
|
loading: boolean;
|
|
25
26
|
data?: TData;
|
|
26
27
|
dataUpdatedMs?: number;
|
|
27
|
-
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Use unknown type for error because we can't assume what type of variable
|
|
31
|
+
* will be thrown from the query function. We also don't want to add this
|
|
32
|
+
* type to QueryStateValue<TError> as it adds complexity to the type.
|
|
33
|
+
*/
|
|
34
|
+
error?: unknown;
|
|
35
|
+
|
|
28
36
|
errorUpdatedMs?: number;
|
|
29
37
|
};
|
|
30
38
|
|
|
31
39
|
export type UseQueryResult<TData> = QueryStateValue<TData> & {
|
|
32
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Cancels the inflight query (if any). Returns true if canceled.
|
|
42
|
+
*/
|
|
43
|
+
abortCurrentQuery: (reason?: unknown) => boolean;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Manually refresh the data by doing a new query.
|
|
47
|
+
*/
|
|
48
|
+
refetch: () => void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* If you got the data from somewhere else (e.g. an in-memory data update),
|
|
52
|
+
* just set it directly.
|
|
53
|
+
*/
|
|
33
54
|
setData: (data: TData, dataUpdatedMs?: number) => void;
|
|
55
|
+
|
|
34
56
|
deleteData: () => void;
|
|
35
57
|
};
|
|
36
58
|
|
|
@@ -48,7 +70,7 @@ export type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
|
48
70
|
queryName: string;
|
|
49
71
|
|
|
50
72
|
// Function to fetch data.
|
|
51
|
-
queryFn:
|
|
73
|
+
queryFn: QueryFn<TArgs, TData>;
|
|
52
74
|
|
|
53
75
|
// ms before data considered stale; 0 means always stale.
|
|
54
76
|
staleMs?: number;
|
|
@@ -71,60 +93,81 @@ export type SharedQueryOptions<TArgs extends unknown[], TData> = {
|
|
|
71
93
|
// Max number of bytes to keep. The oldest entries will be discarded.
|
|
72
94
|
// The last record cannot be discarded. 0 means no limit.
|
|
73
95
|
maxBytes?: number;
|
|
96
|
+
|
|
97
|
+
// Customize the logger.
|
|
98
|
+
log?: (...args: unknown[]) => void;
|
|
74
99
|
} & SharedStateOptions<SharedQueryState<TData>>;
|
|
75
100
|
|
|
76
101
|
/** Users can override these values globally. */
|
|
77
102
|
export const SharedQueryDefaults = {
|
|
103
|
+
// Refetch on every page load.
|
|
78
104
|
staleMs: 0,
|
|
105
|
+
|
|
106
|
+
// Keep in cache for a long time.
|
|
79
107
|
expiryMs: 30 * MS_PER_DAY,
|
|
108
|
+
|
|
109
|
+
// Always refetch automatically if the data is stale.
|
|
110
|
+
refetchOnStale: true,
|
|
111
|
+
|
|
112
|
+
// No default persistence configured.
|
|
80
113
|
persistTo: undefined as PersistTo | undefined,
|
|
81
|
-
|
|
82
|
-
|
|
114
|
+
|
|
115
|
+
// 100 is not too large, but please tweak accordingly.
|
|
116
|
+
maxSize: 100,
|
|
117
|
+
|
|
118
|
+
// 100kb limit before discarding old records.
|
|
119
|
+
maxBytes: 100_000,
|
|
120
|
+
|
|
121
|
+
// Default to log to console.
|
|
122
|
+
log: (...args: unknown[]) => console.log("[sharedQuery]", ...args),
|
|
83
123
|
};
|
|
84
124
|
|
|
85
125
|
export class SharedQuery<TArgs extends unknown[], TData> {
|
|
86
|
-
private readonly
|
|
126
|
+
private readonly inflightQueries = new Map<
|
|
127
|
+
string,
|
|
128
|
+
{ promise: Promise<TData>; abortController: AbortController }
|
|
129
|
+
>();
|
|
130
|
+
|
|
87
131
|
public readonly queryState: SharedState<SharedQueryState<TData>>;
|
|
88
132
|
|
|
89
133
|
public readonly queryName: string;
|
|
90
|
-
private readonly queryFn:
|
|
134
|
+
private readonly queryFn: QueryFn<TArgs, TData>;
|
|
91
135
|
public readonly expiryMs: number;
|
|
92
136
|
public readonly staleMs: number;
|
|
93
137
|
public readonly refetchOnStale: boolean;
|
|
94
138
|
public readonly maxSize: number;
|
|
95
139
|
public readonly maxBytes: number;
|
|
140
|
+
public readonly log: (...args: unknown[]) => void;
|
|
96
141
|
|
|
97
142
|
public constructor(options: SharedQueryOptions<TArgs, TData>) {
|
|
98
143
|
this.queryName = options.queryName;
|
|
99
|
-
|
|
100
144
|
this.queryFn = options.queryFn;
|
|
101
145
|
|
|
102
|
-
this.
|
|
103
|
-
|
|
104
|
-
? options?.expiryMs
|
|
105
|
-
: SharedQueryDefaults.expiryMs;
|
|
106
|
-
|
|
107
|
-
this.staleMs =
|
|
108
|
-
options?.staleMs !== undefined
|
|
109
|
-
? options?.staleMs
|
|
110
|
-
: SharedQueryDefaults.staleMs;
|
|
146
|
+
this.staleMs = options?.staleMs ?? SharedQueryDefaults.staleMs;
|
|
147
|
+
this.expiryMs = options?.expiryMs ?? SharedQueryDefaults.expiryMs;
|
|
111
148
|
|
|
112
|
-
this.refetchOnStale =
|
|
149
|
+
this.refetchOnStale =
|
|
150
|
+
options?.refetchOnStale ?? SharedQueryDefaults.refetchOnStale;
|
|
113
151
|
|
|
114
152
|
// Apply shortcut when using `persistTo`.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
153
|
+
// Specifying the storage keys will take precedence over `persistTo`.
|
|
154
|
+
if (!options.localStorageKey && !options.indexedDbKey) {
|
|
155
|
+
const persistTo = options.persistTo ?? SharedQueryDefaults.persistTo;
|
|
156
|
+
|
|
157
|
+
if (persistTo === "localStorage") {
|
|
158
|
+
options.localStorageKey = options.queryName;
|
|
159
|
+
} else if (persistTo === "indexedDb") {
|
|
160
|
+
options.indexedDbKey = options.queryName;
|
|
161
|
+
}
|
|
121
162
|
}
|
|
122
163
|
|
|
123
164
|
this.maxSize = options?.maxSize ?? SharedQueryDefaults.maxSize;
|
|
124
165
|
this.maxBytes = options?.maxBytes ?? SharedQueryDefaults.maxBytes;
|
|
125
166
|
|
|
167
|
+
this.log = options?.log ?? SharedQueryDefaults.log;
|
|
168
|
+
|
|
126
169
|
this.queryState = sharedState<SharedQueryState<TData>>(
|
|
127
|
-
{},
|
|
170
|
+
{}, // defaultValue
|
|
128
171
|
{
|
|
129
172
|
...options,
|
|
130
173
|
// Fallback to use expiryMs for the storeExpiryMs.
|
|
@@ -133,10 +176,27 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
133
176
|
);
|
|
134
177
|
}
|
|
135
178
|
|
|
179
|
+
/** Returns a key to identify requests based on the given args. */
|
|
136
180
|
public getQueryKey(args: TArgs): string {
|
|
137
181
|
return this.queryName + ":" + stringifyDeterministicForKeys(args);
|
|
138
182
|
}
|
|
139
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Get the AbortController to abort the inflight query (if any).
|
|
186
|
+
*
|
|
187
|
+
* Note that once you get the controller, you can also get the signal easily
|
|
188
|
+
* using `controller.signal`. So if you need both the controller and the
|
|
189
|
+
* signal, just use this function.
|
|
190
|
+
*/
|
|
191
|
+
public getAbortController(queryKey: string): AbortController | null {
|
|
192
|
+
return this.inflightQueries.get(queryKey)?.abortController ?? null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Get the AbortSignal to check for query abortions. */
|
|
196
|
+
public getAbortSignal(queryKey: string): AbortSignal | null {
|
|
197
|
+
return this.getAbortController(queryKey)?.signal ?? null;
|
|
198
|
+
}
|
|
199
|
+
|
|
140
200
|
private isStale(dataUpdatedMs: number): boolean {
|
|
141
201
|
if (this.staleMs === 0) return true; // always stale
|
|
142
202
|
|
|
@@ -158,20 +218,18 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
158
218
|
if (cached?.data && !this.isExpired(cached.dataUpdatedMs ?? 0)) {
|
|
159
219
|
// Return cached value if not stale.
|
|
160
220
|
if (!this.isStale(cached.dataUpdatedMs ?? 0)) {
|
|
161
|
-
|
|
162
|
-
`Return fresh data ${queryKey} from cache without fetching`,
|
|
163
|
-
);
|
|
221
|
+
this.log(`Return fresh data ${queryKey} from cache without fetching`);
|
|
164
222
|
return cached.data;
|
|
165
223
|
}
|
|
166
224
|
|
|
167
225
|
// If stale, optionally update the data in the background.
|
|
168
226
|
if (this.refetchOnStale) {
|
|
169
|
-
|
|
227
|
+
this.log(`refetchOnStale for ${queryKey}`);
|
|
170
228
|
void this.dedupedFetch(queryKey, ...args);
|
|
171
229
|
}
|
|
172
230
|
|
|
173
231
|
// Still return stale data immediately.
|
|
174
|
-
|
|
232
|
+
this.log(`Returning stale data for ${queryKey}`);
|
|
175
233
|
return cached.data;
|
|
176
234
|
}
|
|
177
235
|
|
|
@@ -179,35 +237,37 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
179
237
|
}
|
|
180
238
|
|
|
181
239
|
private dedupedFetch(queryKey: string, ...args: TArgs): Promise<TData> {
|
|
182
|
-
const
|
|
240
|
+
const inflightQuery = this.inflightQueries.get(queryKey);
|
|
183
241
|
|
|
184
242
|
// De-duplicate in-flight requests
|
|
185
|
-
if (
|
|
186
|
-
|
|
187
|
-
return
|
|
243
|
+
if (inflightQuery) {
|
|
244
|
+
this.log(`Deduplicating inflight fetch for ${queryKey}`);
|
|
245
|
+
return inflightQuery.promise;
|
|
188
246
|
}
|
|
189
247
|
|
|
190
248
|
// Fetch new data
|
|
191
249
|
const promise = this.startFetching(queryKey, args);
|
|
250
|
+
const abortController = new AbortController();
|
|
251
|
+
|
|
252
|
+
this.inflightQueries.set(queryKey, { promise, abortController });
|
|
192
253
|
|
|
193
|
-
this.inflightPromises.set(queryKey, promise);
|
|
194
254
|
return promise;
|
|
195
255
|
}
|
|
196
256
|
|
|
197
257
|
private async startFetching(queryKey: string, args: TArgs) {
|
|
198
|
-
|
|
258
|
+
this.log(`Start fetching ${queryKey}`);
|
|
199
259
|
|
|
200
260
|
try {
|
|
201
261
|
const data = await this.queryFn(...args);
|
|
202
262
|
|
|
203
|
-
|
|
263
|
+
this.log(`Successfully fetched ${queryKey}`);
|
|
204
264
|
this.setData(queryKey, data, Date.now());
|
|
205
265
|
return data;
|
|
206
266
|
} catch (e) {
|
|
207
|
-
|
|
267
|
+
this.log(`Failed to fetch ${queryKey}: ${e}`);
|
|
208
268
|
throw e;
|
|
209
269
|
} finally {
|
|
210
|
-
this.
|
|
270
|
+
this.inflightQueries.delete(queryKey);
|
|
211
271
|
}
|
|
212
272
|
}
|
|
213
273
|
|
|
@@ -236,21 +296,30 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
236
296
|
}
|
|
237
297
|
|
|
238
298
|
/** Delete the cached data for one set of arguments. */
|
|
239
|
-
public deleteData(
|
|
240
|
-
const queryKey = this.getQueryKey(args);
|
|
241
|
-
|
|
299
|
+
public deleteData(queryKey: string): void {
|
|
242
300
|
const record = this.queryState.getSnapshot();
|
|
243
301
|
const clone = { ...record };
|
|
244
302
|
delete clone[queryKey];
|
|
245
303
|
this.queryState.setValue(clone);
|
|
246
304
|
|
|
247
|
-
this.
|
|
305
|
+
const inflight = this.inflightQueries.get(queryKey);
|
|
306
|
+
if (inflight) {
|
|
307
|
+
this.inflightQueries.delete(queryKey);
|
|
308
|
+
inflight.abortController.abort("Deleted");
|
|
309
|
+
}
|
|
248
310
|
}
|
|
249
311
|
|
|
250
312
|
/** Delete all currently cached data & all inflight promises. */
|
|
251
313
|
public clear(): void {
|
|
252
314
|
this.queryState.delete();
|
|
253
|
-
|
|
315
|
+
|
|
316
|
+
for (const { abortController } of Array.from(
|
|
317
|
+
this.inflightQueries.values(),
|
|
318
|
+
)) {
|
|
319
|
+
abortController.abort("Cleared");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
this.inflightQueries.clear();
|
|
254
323
|
}
|
|
255
324
|
|
|
256
325
|
/**
|
|
@@ -281,7 +350,7 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
281
350
|
const numToCut = oldSize - this.maxSize;
|
|
282
351
|
|
|
283
352
|
if (numToCut > 0) {
|
|
284
|
-
|
|
353
|
+
this.log(
|
|
285
354
|
`enforceSizeLimit for ${this.queryName} needed due to size ${oldSize} > ${this.maxSize}`,
|
|
286
355
|
);
|
|
287
356
|
|
|
@@ -309,7 +378,7 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
309
378
|
let bytesToCut = currentByteSize - this.maxBytes;
|
|
310
379
|
|
|
311
380
|
if (bytesToCut > 0) {
|
|
312
|
-
|
|
381
|
+
this.log(
|
|
313
382
|
`enforceSizeLimit for ${this.queryName} needed due to byte size ${currentByteSize.toLocaleString()} > ${this.maxBytes.toLocaleString()}`,
|
|
314
383
|
);
|
|
315
384
|
|
|
@@ -342,7 +411,7 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
342
411
|
this.queryState.setValue(newState);
|
|
343
412
|
|
|
344
413
|
// Log to inform user that the data was trimmed.
|
|
345
|
-
|
|
414
|
+
this.log(
|
|
346
415
|
`Trimmed data for ${this.queryName}: size=(${oldSize} -> ${Object.keys(newState).length}) (limit=${this.maxSize}), byteSize=(${oldByteSize.toLocaleString()} -> ${getByteSize(newState).toLocaleString()}) (limit=${this.maxBytes.toLocaleString()})`,
|
|
347
416
|
);
|
|
348
417
|
|
|
@@ -350,7 +419,7 @@ export class SharedQuery<TArgs extends unknown[], TData> {
|
|
|
350
419
|
}
|
|
351
420
|
|
|
352
421
|
// Log to indicate why trimming was not needed.
|
|
353
|
-
|
|
422
|
+
this.log(
|
|
354
423
|
`No need to trim data for ${this.queryName}: size=${oldSize} (limit=${this.maxSize}), byteSize=${oldByteSize.toLocaleString()} (limit=${this.maxBytes.toLocaleString()})`,
|
|
355
424
|
);
|
|
356
425
|
}
|
|
@@ -406,80 +475,88 @@ export function useSharedQuery<TArgs extends unknown[], TData>(
|
|
|
406
475
|
// Default to use no arguments.
|
|
407
476
|
args: TArgs = [] as unknown as TArgs,
|
|
408
477
|
): UseQueryResult<TData> {
|
|
478
|
+
// We don't expect users to provide a stable `args`, so stabilize it here.
|
|
479
|
+
const stableArgs = useDeepMemo(args);
|
|
480
|
+
|
|
409
481
|
const [queryState, setQueryState] = useSharedState(query.queryState);
|
|
410
482
|
|
|
411
483
|
const isMounted = useRef(true);
|
|
412
484
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
485
|
+
const queryKey = useMemo(
|
|
486
|
+
() => query.getQueryKey(stableArgs),
|
|
487
|
+
[query, stableArgs],
|
|
488
|
+
);
|
|
417
489
|
|
|
418
|
-
//
|
|
419
|
-
const
|
|
420
|
-
|
|
490
|
+
// Make sure typechecker knows this could be undefined.
|
|
491
|
+
const queryStateValue = queryState[queryKey] as
|
|
492
|
+
| QueryStateValue<TData>
|
|
493
|
+
| undefined;
|
|
421
494
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
495
|
+
// The fetch logic wrapped in useCallback to be stable for useEffect
|
|
496
|
+
const execute = useCallback(
|
|
497
|
+
async (forceRefresh: boolean) => {
|
|
498
|
+
query.log(`Begin executing shared query ${queryKey}`);
|
|
499
|
+
|
|
500
|
+
setQueryState((prev) => {
|
|
501
|
+
const clone = { ...prev };
|
|
502
|
+
|
|
503
|
+
clone[queryKey] = {
|
|
504
|
+
...(clone[queryKey] ?? {}),
|
|
505
|
+
lastUpdatedMs: Date.now(),
|
|
506
|
+
loading: true,
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
return clone;
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
try {
|
|
513
|
+
const data = await (forceRefresh
|
|
514
|
+
? query.updateFromSource(...stableArgs)
|
|
515
|
+
: query.getCachedOrFetch(...stableArgs));
|
|
516
|
+
|
|
517
|
+
if (isMounted.current) {
|
|
518
|
+
setQueryState((prev) => {
|
|
519
|
+
const clone = { ...prev };
|
|
520
|
+
const now = Date.now();
|
|
521
|
+
|
|
522
|
+
clone[queryKey] = {
|
|
523
|
+
// Don't keep old error.
|
|
524
|
+
lastUpdatedMs: now,
|
|
525
|
+
loading: false,
|
|
526
|
+
data,
|
|
527
|
+
dataUpdatedMs: now,
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
return clone;
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
} catch (e) {
|
|
534
|
+
if (isMounted.current) {
|
|
535
|
+
setQueryState((prev) => {
|
|
536
|
+
const clone = { ...prev };
|
|
537
|
+
const now = Date.now();
|
|
538
|
+
|
|
539
|
+
clone[queryKey] = {
|
|
540
|
+
// Keep old data.
|
|
541
|
+
...(clone[queryKey] ?? {}),
|
|
542
|
+
lastUpdatedMs: now,
|
|
543
|
+
loading: false,
|
|
544
|
+
error: e,
|
|
545
|
+
errorUpdatedMs: now,
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
return clone;
|
|
549
|
+
});
|
|
550
|
+
}
|
|
475
551
|
}
|
|
476
|
-
}
|
|
477
|
-
|
|
552
|
+
},
|
|
553
|
+
[query, queryKey, setQueryState, stableArgs],
|
|
554
|
+
);
|
|
478
555
|
|
|
479
556
|
useEffect(() => {
|
|
480
557
|
isMounted.current = true;
|
|
481
558
|
|
|
482
|
-
void execute();
|
|
559
|
+
void execute(false);
|
|
483
560
|
|
|
484
561
|
return () => {
|
|
485
562
|
isMounted.current = false;
|
|
@@ -488,20 +565,24 @@ export function useSharedQuery<TArgs extends unknown[], TData>(
|
|
|
488
565
|
|
|
489
566
|
return useMemo(() => {
|
|
490
567
|
const state =
|
|
491
|
-
|
|
492
|
-
(DEFAULT_QUERY_STATE_ENTRY as QueryStateValue<TData>);
|
|
568
|
+
queryStateValue ?? (DEFAULT_QUERY_STATE_ENTRY as QueryStateValue<TData>);
|
|
493
569
|
|
|
494
570
|
return {
|
|
495
571
|
...state,
|
|
572
|
+
abortCurrentQuery: (reason?: unknown) => {
|
|
573
|
+
const controller = query.getAbortController(queryKey);
|
|
574
|
+
controller?.abort(reason);
|
|
575
|
+
return Boolean(controller);
|
|
576
|
+
},
|
|
496
577
|
refetch: () => {
|
|
497
|
-
|
|
578
|
+
void execute(true);
|
|
498
579
|
},
|
|
499
580
|
setData: (data: TData, dataUpdatedMs?: number) => {
|
|
500
581
|
query.setData(queryKey, data, dataUpdatedMs ?? Date.now());
|
|
501
582
|
},
|
|
502
583
|
deleteData: () => {
|
|
503
|
-
query.deleteData(
|
|
584
|
+
query.deleteData(queryKey);
|
|
504
585
|
},
|
|
505
586
|
};
|
|
506
|
-
}, [query, queryKey,
|
|
587
|
+
}, [execute, query, queryKey, queryStateValue]);
|
|
507
588
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import isEqual from "lodash/isEqual";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
|
|
4
|
+
/** Provides a stable value for T based on deep equality. */
|
|
5
|
+
export function useDeepMemo<T>(value: T): T {
|
|
6
|
+
const [state, setState] = useState<T>(value);
|
|
7
|
+
|
|
8
|
+
return useMemo(() => {
|
|
9
|
+
if (state === value || isEqual(state, value)) {
|
|
10
|
+
return state;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// We check equality, so this should be safe.
|
|
14
|
+
// eslint-disable-next-line react-hooks/set-state-in-render
|
|
15
|
+
setState(value);
|
|
16
|
+
|
|
17
|
+
return value;
|
|
18
|
+
}, [state, value]);
|
|
19
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/utils/useDeepMemo.ts
|
|
31
|
+
var useDeepMemo_exports = {};
|
|
32
|
+
__export(useDeepMemo_exports, {
|
|
33
|
+
useDeepMemo: () => useDeepMemo
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(useDeepMemo_exports);
|
|
36
|
+
var import_isEqual = __toESM(require("lodash/isEqual"));
|
|
37
|
+
var import_react = require("react");
|
|
38
|
+
function useDeepMemo(value) {
|
|
39
|
+
const [state, setState] = (0, import_react.useState)(value);
|
|
40
|
+
return (0, import_react.useMemo)(() => {
|
|
41
|
+
if (state === value || (0, import_isEqual.default)(state, value)) {
|
|
42
|
+
return state;
|
|
43
|
+
}
|
|
44
|
+
setState(value);
|
|
45
|
+
return value;
|
|
46
|
+
}, [state, value]);
|
|
47
|
+
}
|
|
48
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
49
|
+
0 && (module.exports = {
|
|
50
|
+
useDeepMemo
|
|
51
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/utils/useDeepMemo.ts
|
|
2
|
+
import isEqual from "lodash/isEqual";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
function useDeepMemo(value) {
|
|
5
|
+
const [state, setState] = useState(value);
|
|
6
|
+
return useMemo(() => {
|
|
7
|
+
if (state === value || isEqual(state, value)) {
|
|
8
|
+
return state;
|
|
9
|
+
}
|
|
10
|
+
setState(value);
|
|
11
|
+
return value;
|
|
12
|
+
}, [state, value]);
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
useDeepMemo
|
|
16
|
+
};
|
package/docs/.nojekyll
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
|
package/docs/assets/hierarchy.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg=="
|