@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.
Files changed (56) hide show
  1. package/README.md +3 -1
  2. package/package.json +7 -2
  3. package/sharedQuery.d.mts +38 -7
  4. package/sharedQuery.d.ts +37 -6
  5. package/sharedQuery.js +149 -89
  6. package/sharedQuery.mjs +147 -87
  7. package/src/sharedQuery.ts +198 -117
  8. package/src/utils/useDeepMemo.ts +19 -0
  9. package/utils/useDeepMemo.d.mts +4 -0
  10. package/utils/useDeepMemo.d.ts +2 -0
  11. package/utils/useDeepMemo.js +51 -0
  12. package/utils/useDeepMemo.mjs +16 -0
  13. package/docs/.nojekyll +0 -1
  14. package/docs/assets/hierarchy.js +0 -1
  15. package/docs/assets/highlight.css +0 -71
  16. package/docs/assets/icons.js +0 -18
  17. package/docs/assets/icons.svg +0 -1
  18. package/docs/assets/main.js +0 -60
  19. package/docs/assets/navigation.js +0 -1
  20. package/docs/assets/search.js +0 -1
  21. package/docs/assets/style.css +0 -1633
  22. package/docs/classes/sharedQuery.SharedQuery.html +0 -22
  23. package/docs/classes/sharedState.SharedState.html +0 -9
  24. package/docs/classes/utils_storage.InMemoryStorageAdapter.html +0 -9
  25. package/docs/classes/utils_storage.IndexedDbAdapter.html +0 -9
  26. package/docs/classes/utils_storage.LocalStorageAdapter.html +0 -9
  27. package/docs/functions/sharedQuery.sharedQuery.html +0 -9
  28. package/docs/functions/sharedQuery.useSharedQuery.html +0 -6
  29. package/docs/functions/sharedState.sharedState.html +0 -3
  30. package/docs/functions/sharedState.useSharedState.html +0 -3
  31. package/docs/functions/sharedState.useSharedStateSetter.html +0 -2
  32. package/docs/functions/sharedState.useSharedStateValue.html +0 -2
  33. package/docs/functions/utils_storage.getStorageAdapter.html +0 -1
  34. package/docs/functions/utils_stringify.stringifyDeterministicForKeys.html +0 -7
  35. package/docs/hierarchy.html +0 -1
  36. package/docs/index.html +0 -2
  37. package/docs/modules/fetcher.html +0 -1
  38. package/docs/modules/index.html +0 -1
  39. package/docs/modules/sharedQuery.html +0 -1
  40. package/docs/modules/sharedState.html +0 -1
  41. package/docs/modules/utils_storage.html +0 -1
  42. package/docs/modules/utils_stringify.html +0 -1
  43. package/docs/modules.html +0 -1
  44. package/docs/types/sharedQuery.FetchFn.html +0 -1
  45. package/docs/types/sharedQuery.PersistTo.html +0 -1
  46. package/docs/types/sharedQuery.QueryStateValue.html +0 -9
  47. package/docs/types/sharedQuery.SharedQueryOptions.html +0 -1
  48. package/docs/types/sharedQuery.SharedQueryState.html +0 -3
  49. package/docs/types/sharedQuery.UseQueryResult.html +0 -1
  50. package/docs/types/sharedState.SharedStateOptions.html +0 -1
  51. package/docs/types/utils_storage.StorageAdapter.html +0 -5
  52. package/docs/types/utils_storage.StorageOptions.html +0 -5
  53. package/docs/variables/fetcher.fetcher.html +0 -2
  54. package/docs/variables/fetcher.fetcherWithRetries.html +0 -2
  55. package/docs/variables/sharedQuery.SharedQueryDefaults.html +0 -2
  56. package/docs/variables/sharedState.SharedStateDefaults.html +0 -6
@@ -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 FetchFn<TArgs extends unknown[], TData> = (
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
- error?: Error;
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
- refetch: () => Promise<TData>;
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: FetchFn<TArgs, TData>;
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
- maxSize: 100, // 100 is not too large, but please tweak accordingly
82
- maxBytes: 100_000, // 100kb before GC
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 inflightPromises = new Map<string, Promise<TData>>();
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: FetchFn<TArgs, TData>;
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.expiryMs =
103
- options?.expiryMs !== undefined
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 = Boolean(options?.refetchOnStale);
149
+ this.refetchOnStale =
150
+ options?.refetchOnStale ?? SharedQueryDefaults.refetchOnStale;
113
151
 
114
152
  // Apply shortcut when using `persistTo`.
115
- const persistTo = options.persistTo ?? SharedQueryDefaults.persistTo;
116
-
117
- if (persistTo === "localStorage") {
118
- options.localStorageKey = options.queryName;
119
- } else if (persistTo === "indexedDb") {
120
- options.indexedDbKey = options.queryName;
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
- console.log(
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
- console.log(`refetchOnStale for ${queryKey}`);
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
- console.log(`Returning stale data for ${queryKey}`);
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 inflightPromise = this.inflightPromises.get(queryKey);
240
+ const inflightQuery = this.inflightQueries.get(queryKey);
183
241
 
184
242
  // De-duplicate in-flight requests
185
- if (inflightPromise) {
186
- console.log(`Deduplicating inflight fetch for ${queryKey}`);
187
- return inflightPromise;
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
- console.log(`Start fetching ${queryKey}`);
258
+ this.log(`Start fetching ${queryKey}`);
199
259
 
200
260
  try {
201
261
  const data = await this.queryFn(...args);
202
262
 
203
- console.log(`Successfully fetched ${queryKey}`);
263
+ this.log(`Successfully fetched ${queryKey}`);
204
264
  this.setData(queryKey, data, Date.now());
205
265
  return data;
206
266
  } catch (e) {
207
- console.error(`Failed to fetch ${queryKey}: ${e}`);
267
+ this.log(`Failed to fetch ${queryKey}: ${e}`);
208
268
  throw e;
209
269
  } finally {
210
- this.inflightPromises.delete(queryKey);
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(args: TArgs): void {
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.inflightPromises.delete(queryKey);
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
- this.inflightPromises.clear();
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
- console.log(
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
- console.log(
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
- console.log(
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
- console.log(
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
- // Identify args changes using deep equality.
414
- // We intentionally chose not to memoize this value because args is likely
415
- // to be an unstable value (changes on every render).
416
- const queryKey = query.getQueryKey(args);
485
+ const queryKey = useMemo(
486
+ () => query.getQueryKey(stableArgs),
487
+ [query, stableArgs],
488
+ );
417
489
 
418
- // The fetch logic wrapped in useCallback to be stable for useEffect
419
- const execute = useCallback(async () => {
420
- console.log(`Begin executing shared query ${queryKey}`);
490
+ // Make sure typechecker knows this could be undefined.
491
+ const queryStateValue = queryState[queryKey] as
492
+ | QueryStateValue<TData>
493
+ | undefined;
421
494
 
422
- setQueryState((prev) => {
423
- const clone = { ...prev };
424
-
425
- clone[queryKey] = {
426
- ...(clone[queryKey] ?? {}),
427
- lastUpdatedMs: Date.now(),
428
- loading: true,
429
- };
430
-
431
- return clone;
432
- });
433
-
434
- try {
435
- const data = await query.getCachedOrFetch(...args);
436
-
437
- if (isMounted.current) {
438
- setQueryState((prev) => {
439
- const clone = { ...prev };
440
- const now = Date.now();
441
-
442
- clone[queryKey] = {
443
- // Don't keep old error.
444
- lastUpdatedMs: now,
445
- loading: false,
446
- data,
447
- dataUpdatedMs: now,
448
- };
449
-
450
- return clone;
451
- });
452
- }
453
- } catch (e) {
454
- const error =
455
- e instanceof Error
456
- ? e
457
- : new Error(`An unknown error occurred during fetch: ${e}`);
458
-
459
- if (isMounted.current) {
460
- setQueryState((prev) => {
461
- const clone = { ...prev };
462
- const now = Date.now();
463
-
464
- clone[queryKey] = {
465
- // Keep old data.
466
- ...(clone[queryKey] ?? {}),
467
- lastUpdatedMs: now,
468
- loading: false,
469
- error,
470
- errorUpdatedMs: now,
471
- };
472
-
473
- return clone;
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
- }, [queryKey]);
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
- queryState[queryKey] ??
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
- return query.updateFromSource(...args);
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(args);
584
+ query.deleteData(queryKey);
504
585
  },
505
586
  };
506
- }, [query, queryKey, queryState]);
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,4 @@
1
+ /** Provides a stable value for T based on deep equality. */
2
+ declare function useDeepMemo<T>(value: T): T;
3
+
4
+ export { useDeepMemo };
@@ -0,0 +1,2 @@
1
+ /** Provides a stable value for T based on deep equality. */
2
+ export declare function useDeepMemo<T>(value: T): T;
@@ -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.
@@ -1 +0,0 @@
1
- window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg=="