floppy-disk 2.0.2-beta.1 → 2.1.0-beta.1

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 CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  A lightweight, simple, and powerful state management library.
4
4
 
5
- This library was highly-inspired by [Zustand](https://www.npmjs.com/package/zustand) and [React-Query](https://tanstack.com/query). Both are awesome state manager, but I want to have something like that with **more power** and **less bundle size**.
5
+ This library was highly-inspired by [Zustand](https://www.npmjs.com/package/zustand) and [TanStack-Query](https://tanstack.com/query).
6
+ Both are awesome state manager. That's why this Floppy Disk library behaves like them, but with small DX improvement, **more power**, and **less bundle size**.
6
7
 
7
8
  **Bundle Size Comparison:**
8
9
 
@@ -23,7 +24,7 @@ import { createQuery, createMutation } from 'floppy-disk'; // 8.2 kB (gzipped: 2
23
24
  - Using Zustand & React-Query: https://demo-zustand-react-query.vercel.app/
24
25
  👉 Total: **309.21 kB**
25
26
  - Using Floppy Disk: https://demo-floppy-disk.vercel.app/
26
- 👉 Total: **270.86 kB** 🎉
27
+ 👉 Total: **270.87 kB** 🎉
27
28
 
28
29
  ## Key Features
29
30
 
@@ -50,6 +51,8 @@ import { createQuery, createMutation } from 'floppy-disk'; // 8.2 kB (gzipped: 2
50
51
  - Can be used with literally any asynchronous data fetching client, including GraphQL ✅
51
52
  - Create mutation ✅
52
53
 
54
+ **View official documentation on [floppy-disk.vercel.app](https://floppy-disk.vercel.app/)**
55
+
53
56
  ## Table of Contents
54
57
 
55
58
  - [Key Features](#key-features)
@@ -60,6 +63,7 @@ import { createQuery, createMutation } from 'floppy-disk'; // 8.2 kB (gzipped: 2
60
63
  - [Stores](#stores)
61
64
  - [Query \& Mutation](#query--mutation)
62
65
  - [Query State \& Network Fetching State](#query-state--network-fetching-state)
66
+ - [Inherited from createStores](#inherited-from-createstores)
63
67
  - [Single Query](#single-query)
64
68
  - [Single Query with Params](#single-query-with-params)
65
69
  - [Paginated Query or Infinite Query](#paginated-query-or-infinite-query)
@@ -431,6 +435,35 @@ Here is the flow of the query data state:
431
435
  For network fetching state, we use `isWaiting`.
432
436
  The value will be `true` if the query is called and still waiting for the response.
433
437
 
438
+ ### Inherited from createStores
439
+
440
+ The `createQuery` function inherits functionality from the `createStores` function, allowing us to perform the same result and actions available in `createStores`.
441
+
442
+ ```tsx
443
+ const useMyQuery = createQuery(myQueryFn, {
444
+ // 👇 Same as createStores options
445
+ defaultDeps: undefined,
446
+ onFirstSubscribe: (state) => console.log('onFirstSubscribe', state),
447
+ onSubscribe: (state) => console.log('onSubscribe', state),
448
+ onUnsubscribe: (state) => console.log('onUnsubscribe', state),
449
+ onLastUnsubscribe: (state) => console.log('onLastUnsubscribe', state),
450
+ onBeforeChangeKey: (nextKey, prevKey) => console.log('Store key changed', nextKey, prevKey),
451
+
452
+ // ... other createQuery options
453
+ });
454
+ ```
455
+
456
+ Custom reactivity (dependency array) also works:
457
+
458
+ ```tsx
459
+ function QueryLoader() {
460
+ // This component doesn't care whether the query is success or error.
461
+ // It just listening to network fetching state. 👇
462
+ const { isWaiting } = useMyQuery((state) => [state.isWaiting]);
463
+ return <div>Is network fetching? {String(isWaiting)}</div>;
464
+ }
465
+ ```
466
+
434
467
  ### Single Query
435
468
 
436
469
  ```jsx
@@ -458,22 +491,18 @@ function SingleQuery() {
458
491
 
459
492
  > Example: [https://codesandbox.io/.../examples/react/query](https://codesandbox.io/p/sandbox/github/afiiif/floppy-disk/tree/main/examples/react/query)
460
493
 
461
- Custom reactivity:
462
-
463
- ```jsx
464
- // This component doesn't care whether the query is success or error.
465
- // It just listening to network fetching state. 👇
466
- function SingleQueryLoader() {
467
- const { isWaiting } = useGitHubQuery((state) => [state.isWaiting]);
468
- return <div>Is network fetching? {String(isWaiting)}</div>;
469
- }
470
- ```
471
-
472
494
  Actions:
473
495
 
496
+ Normally, we don't need reactivity for the actions.
497
+ Therefore, using `get` method will be better, since it will not re-render the component when a query state changed.
498
+
474
499
  ```jsx
475
500
  function Actions() {
476
- const { fetch, forceFetch, reset } = useGitHubQuery(() => []);
501
+ const { fetch, forceFetch, reset } = useGitHubQuery.get();
502
+
503
+ // Or like this:
504
+ // const { isLoading, data, error, fetch, forceFetch, reset } = useGitHubQuery();
505
+
477
506
  return (
478
507
  <>
479
508
  <button onClick={fetch}>Call query if the query data is stale</button>
@@ -524,22 +553,14 @@ function MyComponent() {
524
553
  Get data or do something outside component:
525
554
 
526
555
  ```jsx
527
- const getData = () => {
528
- console.log(useGitHubQuery.get().data);
529
- };
530
-
531
- const resetQuery = () => {
532
- useGitHubQuery.get().reset();
533
- };
534
-
535
- function Actions() {
536
- return (
537
- <>
538
- <button onClick={getData}>Get Data</button>
539
- <button onClick={resetQuery}>Reset query</button>
540
- </>
541
- );
542
- }
556
+ const getData = () => console.log(useGitHubQuery.get().data);
557
+ const resetQuery = () => useGitHubQuery.get().reset();
558
+
559
+ // Works just like createStores
560
+ useMyQuery.get(/* ... */);
561
+ useMyQuery.set(/* ... */);
562
+ useMyQuery.subscribe(/* ... */);
563
+ useMyQuery.getSubscribers(/* ... */);
543
564
  ```
544
565
 
545
566
  ### Single Query with Params
package/esm/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export { hashStoreKey } from './utils';
1
2
  export * from './vanilla';
2
3
  export * from './react';
package/esm/index.js CHANGED
@@ -1,2 +1,3 @@
1
+ export { hashStoreKey } from './utils';
1
2
  export * from './vanilla';
2
3
  export * from './react';
@@ -4,6 +4,10 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
4
4
  * Query store key, an object that will be hashed into a string as a query store identifier.
5
5
  */
6
6
  key: TKey;
7
+ /**
8
+ * Query store key hash, a string used as a query store identifier.
9
+ */
10
+ keyHash: string;
7
11
  /**
8
12
  * Will only be called if the data is stale or empty.
9
13
  */
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'preact/hooks';
2
- import { hashStoreKey, identityFn, noop } from '../utils';
2
+ import { identityFn, noop } from '../utils';
3
3
  import { createStores } from './create-stores';
4
4
  const getDecision = (value, param, { ifTrue, ifAlways }) => {
5
5
  if (value === true || (typeof value === 'function' && value(param) === true)) {
@@ -9,7 +9,6 @@ const getDecision = (value, param, { ifTrue, ifAlways }) => {
9
9
  ifAlways();
10
10
  }
11
11
  };
12
- const DEFAULT_STALE_TIME = 3000; // 3 seconds
13
12
  const INITIAL_QUERY_STATE = {
14
13
  isWaiting: false,
15
14
  isWaitingNextPage: false,
@@ -41,11 +40,13 @@ const useQueryDefaultDeps = (state) => [
41
40
  state.hasNextPage,
42
41
  ];
43
42
  export const createQuery = (queryFn, options = {}) => {
44
- const { onFirstSubscribe = noop, onSubscribe = noop, onLastUnsubscribe = noop, onBeforeChangeKey = noop, defaultDeps = useQueryDefaultDeps, select = identityFn, staleTime = DEFAULT_STALE_TIME, fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, keepPreviousData, getNextPageParam = () => undefined, onSuccess = noop, onError = noop, onSettled = noop, hashKeyFn = hashStoreKey, ...createStoresOptions } = options;
43
+ const { onFirstSubscribe = noop, onSubscribe = noop, onLastUnsubscribe = noop, onBeforeChangeKey = noop, defaultDeps = useQueryDefaultDeps, select = identityFn, staleTime = 3000, // 3 seconds
44
+ fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, // 3 seconds
45
+ keepPreviousData, getNextPageParam = () => undefined, onSuccess = noop, onError = noop, onSettled = noop, ...createStoresOptions } = options;
45
46
  const retryTimeoutId = new Map();
46
47
  const retryNextPageTimeoutId = new Map();
47
48
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
48
- const useQuery = createStores(({ key: _key, get, set }) => {
49
+ const useQuery = createStores(({ get, set, key: _key, keyHash }) => {
49
50
  const key = _key;
50
51
  const getRetryProps = (error, retryCount) => {
51
52
  const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
@@ -70,13 +71,13 @@ export const createQuery = (queryFn, options = {}) => {
70
71
  set({ isGoingToRetry: false, isWaiting: true });
71
72
  else
72
73
  set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
73
- clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
74
+ clearTimeout(retryTimeoutId.get(keyHash));
74
75
  }
75
76
  const stateBeforeCallQuery = { ...get(), pageParam };
76
- preventReplaceResponse.set(hashKeyFn(key), false);
77
+ preventReplaceResponse.set(keyHash, false);
77
78
  queryFn(key, stateBeforeCallQuery)
78
79
  .then((response) => {
79
- if (preventReplaceResponse.get(hashKeyFn(key))) {
80
+ if (preventReplaceResponse.get(keyHash)) {
80
81
  set({ isWaiting: false });
81
82
  return;
82
83
  }
@@ -144,7 +145,7 @@ export const createQuery = (queryFn, options = {}) => {
144
145
  hasNextPage: pageParam !== undefined,
145
146
  });
146
147
  if (shouldRetry) {
147
- retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
148
+ retryTimeoutId.set(keyHash, window.setTimeout(() => {
148
149
  set({ retryCount: prevState.retryCount + 1 });
149
150
  callQuery();
150
151
  }, delay));
@@ -172,7 +173,7 @@ export const createQuery = (queryFn, options = {}) => {
172
173
  if (isWaitingNextPage || !hasNextPage)
173
174
  return;
174
175
  set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
175
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
176
+ clearTimeout(retryNextPageTimeoutId.get(keyHash));
176
177
  queryFn(key, { ...state, pageParam })
177
178
  .then((response) => {
178
179
  const newPageParam = getNextPageParam(response, pageParams.length);
@@ -197,7 +198,7 @@ export const createQuery = (queryFn, options = {}) => {
197
198
  isGoingToRetryNextPage: shouldRetry,
198
199
  });
199
200
  if (shouldRetry) {
200
- retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
201
+ retryNextPageTimeoutId.set(keyHash, window.setTimeout(() => {
201
202
  set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
202
203
  fetchNextPage();
203
204
  }, delay));
@@ -207,6 +208,7 @@ export const createQuery = (queryFn, options = {}) => {
207
208
  return {
208
209
  ...INITIAL_QUERY_STATE,
209
210
  key,
211
+ keyHash,
210
212
  fetch,
211
213
  forceFetch,
212
214
  fetchNextPage,
@@ -225,7 +227,6 @@ export const createQuery = (queryFn, options = {}) => {
225
227
  return {
226
228
  ...createStoresOptions,
227
229
  defaultDeps,
228
- hashKeyFn,
229
230
  onFirstSubscribe: (state) => {
230
231
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
231
232
  window.addEventListener('focus', fetchWindowFocusHandler);
@@ -244,8 +245,8 @@ export const createQuery = (queryFn, options = {}) => {
244
245
  window.removeEventListener('focus', fetchWindowFocusHandler);
245
246
  }
246
247
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
247
- clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
248
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
248
+ clearTimeout(retryTimeoutId.get(state.keyHash));
249
+ clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
249
250
  onLastUnsubscribe(state);
250
251
  },
251
252
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -323,7 +324,7 @@ export const createQuery = (queryFn, options = {}) => {
323
324
  response: optimisticResponse,
324
325
  data: select(optimisticResponse, { key: key, data: null }),
325
326
  });
326
- preventReplaceResponse.set(hashKeyFn(key), true);
327
+ preventReplaceResponse.set(prevState.keyHash, true);
327
328
  const revert = () => {
328
329
  useQuery.set(key, {
329
330
  isOptimisticData: false,
@@ -3,9 +3,10 @@ import { WatchProps } from './create-store';
3
3
  type Maybe<T> = T | null | undefined;
4
4
  export type StoreKey = Record<string, any> | undefined;
5
5
  export type StoresInitializer<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = (api: {
6
- key: TKey;
7
6
  get: () => T;
8
7
  set: (value: SetStoreData<T>, silent?: boolean) => void;
8
+ key: TKey;
9
+ keyHash: string;
9
10
  }) => T;
10
11
  export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = {
11
12
  /**
@@ -6,11 +6,11 @@ export const createStores = (initializer, options = {}) => {
6
6
  const stores = new Map();
7
7
  const getStore = (_key) => {
8
8
  const key = _key || {};
9
- const normalizedKey = hashKeyFn(key);
10
- if (!stores.has(normalizedKey)) {
11
- stores.set(normalizedKey, initStore((api) => initializer({ key, ...api }), options));
9
+ const keyHash = hashKeyFn(key);
10
+ if (!stores.has(keyHash)) {
11
+ stores.set(keyHash, initStore((api) => initializer({ key, keyHash, ...api }), options));
12
12
  }
13
- return stores.get(normalizedKey);
13
+ return stores.get(keyHash);
14
14
  };
15
15
  /**
16
16
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -18,9 +18,9 @@ export const createStores = (initializer, options = {}) => {
18
18
  const useStores = (...args) => {
19
19
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
20
20
  const key = _key || {};
21
- const normalizedKey = hashKeyFn(key);
21
+ const keyHash = hashKeyFn(key);
22
22
  // eslint-disable-next-line react-hooks/exhaustive-deps
23
- const { get, subscribe } = useMemo(() => getStore(key), [normalizedKey]);
23
+ const { get, subscribe } = useMemo(() => getStore(key), [keyHash]);
24
24
  const [state, setState] = useState(get);
25
25
  const isFirstRender = useRef(true);
26
26
  const prevKey = useRef(key);
@@ -34,7 +34,7 @@ export const createStores = (initializer, options = {}) => {
34
34
  const unsubs = subscribe(setState, selectDeps);
35
35
  return unsubs;
36
36
  // eslint-disable-next-line react-hooks/exhaustive-deps
37
- }, [normalizedKey]);
37
+ }, [keyHash]);
38
38
  return state;
39
39
  };
40
40
  useStores.get = (key) => {
@@ -4,6 +4,10 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
4
4
  * Query store key, an object that will be hashed into a string as a query store identifier.
5
5
  */
6
6
  key: TKey;
7
+ /**
8
+ * Query store key hash, a string used as a query store identifier.
9
+ */
10
+ keyHash: string;
7
11
  /**
8
12
  * Will only be called if the data is stale or empty.
9
13
  */
@@ -1,5 +1,5 @@
1
1
  import { useState } from 'react';
2
- import { hashStoreKey, identityFn, noop } from '../utils';
2
+ import { identityFn, noop } from '../utils';
3
3
  import { createStores } from './create-stores';
4
4
  const getDecision = (value, param, { ifTrue, ifAlways }) => {
5
5
  if (value === true || (typeof value === 'function' && value(param) === true)) {
@@ -9,7 +9,6 @@ const getDecision = (value, param, { ifTrue, ifAlways }) => {
9
9
  ifAlways();
10
10
  }
11
11
  };
12
- const DEFAULT_STALE_TIME = 3000; // 3 seconds
13
12
  const INITIAL_QUERY_STATE = {
14
13
  isWaiting: false,
15
14
  isWaitingNextPage: false,
@@ -41,11 +40,13 @@ const useQueryDefaultDeps = (state) => [
41
40
  state.hasNextPage,
42
41
  ];
43
42
  export const createQuery = (queryFn, options = {}) => {
44
- const { onFirstSubscribe = noop, onSubscribe = noop, onLastUnsubscribe = noop, onBeforeChangeKey = noop, defaultDeps = useQueryDefaultDeps, select = identityFn, staleTime = DEFAULT_STALE_TIME, fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, keepPreviousData, getNextPageParam = () => undefined, onSuccess = noop, onError = noop, onSettled = noop, hashKeyFn = hashStoreKey, ...createStoresOptions } = options;
43
+ const { onFirstSubscribe = noop, onSubscribe = noop, onLastUnsubscribe = noop, onBeforeChangeKey = noop, defaultDeps = useQueryDefaultDeps, select = identityFn, staleTime = 3000, // 3 seconds
44
+ fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, // 3 seconds
45
+ keepPreviousData, getNextPageParam = () => undefined, onSuccess = noop, onError = noop, onSettled = noop, ...createStoresOptions } = options;
45
46
  const retryTimeoutId = new Map();
46
47
  const retryNextPageTimeoutId = new Map();
47
48
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
48
- const useQuery = createStores(({ key: _key, get, set }) => {
49
+ const useQuery = createStores(({ get, set, key: _key, keyHash }) => {
49
50
  const key = _key;
50
51
  const getRetryProps = (error, retryCount) => {
51
52
  const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
@@ -70,13 +71,13 @@ export const createQuery = (queryFn, options = {}) => {
70
71
  set({ isGoingToRetry: false, isWaiting: true });
71
72
  else
72
73
  set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
73
- clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
74
+ clearTimeout(retryTimeoutId.get(keyHash));
74
75
  }
75
76
  const stateBeforeCallQuery = { ...get(), pageParam };
76
- preventReplaceResponse.set(hashKeyFn(key), false);
77
+ preventReplaceResponse.set(keyHash, false);
77
78
  queryFn(key, stateBeforeCallQuery)
78
79
  .then((response) => {
79
- if (preventReplaceResponse.get(hashKeyFn(key))) {
80
+ if (preventReplaceResponse.get(keyHash)) {
80
81
  set({ isWaiting: false });
81
82
  return;
82
83
  }
@@ -144,7 +145,7 @@ export const createQuery = (queryFn, options = {}) => {
144
145
  hasNextPage: pageParam !== undefined,
145
146
  });
146
147
  if (shouldRetry) {
147
- retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
148
+ retryTimeoutId.set(keyHash, window.setTimeout(() => {
148
149
  set({ retryCount: prevState.retryCount + 1 });
149
150
  callQuery();
150
151
  }, delay));
@@ -172,7 +173,7 @@ export const createQuery = (queryFn, options = {}) => {
172
173
  if (isWaitingNextPage || !hasNextPage)
173
174
  return;
174
175
  set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
175
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
176
+ clearTimeout(retryNextPageTimeoutId.get(keyHash));
176
177
  queryFn(key, { ...state, pageParam })
177
178
  .then((response) => {
178
179
  const newPageParam = getNextPageParam(response, pageParams.length);
@@ -197,7 +198,7 @@ export const createQuery = (queryFn, options = {}) => {
197
198
  isGoingToRetryNextPage: shouldRetry,
198
199
  });
199
200
  if (shouldRetry) {
200
- retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
201
+ retryNextPageTimeoutId.set(keyHash, window.setTimeout(() => {
201
202
  set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
202
203
  fetchNextPage();
203
204
  }, delay));
@@ -207,6 +208,7 @@ export const createQuery = (queryFn, options = {}) => {
207
208
  return {
208
209
  ...INITIAL_QUERY_STATE,
209
210
  key,
211
+ keyHash,
210
212
  fetch,
211
213
  forceFetch,
212
214
  fetchNextPage,
@@ -225,7 +227,6 @@ export const createQuery = (queryFn, options = {}) => {
225
227
  return {
226
228
  ...createStoresOptions,
227
229
  defaultDeps,
228
- hashKeyFn,
229
230
  onFirstSubscribe: (state) => {
230
231
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
231
232
  window.addEventListener('focus', fetchWindowFocusHandler);
@@ -244,8 +245,8 @@ export const createQuery = (queryFn, options = {}) => {
244
245
  window.removeEventListener('focus', fetchWindowFocusHandler);
245
246
  }
246
247
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
247
- clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
248
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
248
+ clearTimeout(retryTimeoutId.get(state.keyHash));
249
+ clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
249
250
  onLastUnsubscribe(state);
250
251
  },
251
252
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -323,7 +324,7 @@ export const createQuery = (queryFn, options = {}) => {
323
324
  response: optimisticResponse,
324
325
  data: select(optimisticResponse, { key: key, data: null }),
325
326
  });
326
- preventReplaceResponse.set(hashKeyFn(key), true);
327
+ preventReplaceResponse.set(prevState.keyHash, true);
327
328
  const revert = () => {
328
329
  useQuery.set(key, {
329
330
  isOptimisticData: false,
@@ -3,9 +3,10 @@ import { WatchProps } from './create-store';
3
3
  type Maybe<T> = T | null | undefined;
4
4
  export type StoreKey = Record<string, any> | undefined;
5
5
  export type StoresInitializer<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = (api: {
6
- key: TKey;
7
6
  get: () => T;
8
7
  set: (value: SetStoreData<T>, silent?: boolean) => void;
8
+ key: TKey;
9
+ keyHash: string;
9
10
  }) => T;
10
11
  export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = {
11
12
  /**
@@ -6,11 +6,11 @@ export const createStores = (initializer, options = {}) => {
6
6
  const stores = new Map();
7
7
  const getStore = (_key) => {
8
8
  const key = _key || {};
9
- const normalizedKey = hashKeyFn(key);
10
- if (!stores.has(normalizedKey)) {
11
- stores.set(normalizedKey, initStore((api) => initializer({ key, ...api }), options));
9
+ const keyHash = hashKeyFn(key);
10
+ if (!stores.has(keyHash)) {
11
+ stores.set(keyHash, initStore((api) => initializer({ key, keyHash, ...api }), options));
12
12
  }
13
- return stores.get(normalizedKey);
13
+ return stores.get(keyHash);
14
14
  };
15
15
  /**
16
16
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -18,9 +18,9 @@ export const createStores = (initializer, options = {}) => {
18
18
  const useStores = (...args) => {
19
19
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
20
20
  const key = _key || {};
21
- const normalizedKey = hashKeyFn(key);
21
+ const keyHash = hashKeyFn(key);
22
22
  // eslint-disable-next-line react-hooks/exhaustive-deps
23
- const { get, subscribe } = useMemo(() => getStore(key), [normalizedKey]);
23
+ const { get, subscribe } = useMemo(() => getStore(key), [keyHash]);
24
24
  const [state, setState] = useState(get);
25
25
  const isFirstRender = useRef(true);
26
26
  const prevKey = useRef(key);
@@ -34,7 +34,7 @@ export const createStores = (initializer, options = {}) => {
34
34
  const unsubs = subscribe(setState, selectDeps);
35
35
  return unsubs;
36
36
  // eslint-disable-next-line react-hooks/exhaustive-deps
37
- }, [normalizedKey]);
37
+ }, [keyHash]);
38
38
  return state;
39
39
  };
40
40
  useStores.get = (key) => {
package/lib/index.d.ts CHANGED
@@ -1,2 +1,3 @@
1
+ export { hashStoreKey } from './utils';
1
2
  export * from './vanilla';
2
3
  export * from './react';
package/lib/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hashStoreKey = void 0;
3
4
  const tslib_1 = require("tslib");
5
+ var utils_1 = require("./utils");
6
+ Object.defineProperty(exports, "hashStoreKey", { enumerable: true, get: function () { return utils_1.hashStoreKey; } });
4
7
  tslib_1.__exportStar(require("./vanilla"), exports);
5
8
  tslib_1.__exportStar(require("./react"), exports);
@@ -4,6 +4,10 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
4
4
  * Query store key, an object that will be hashed into a string as a query store identifier.
5
5
  */
6
6
  key: TKey;
7
+ /**
8
+ * Query store key hash, a string used as a query store identifier.
9
+ */
10
+ keyHash: string;
7
11
  /**
8
12
  * Will only be called if the data is stale or empty.
9
13
  */
@@ -12,7 +12,6 @@ const getDecision = (value, param, { ifTrue, ifAlways }) => {
12
12
  ifAlways();
13
13
  }
14
14
  };
15
- const DEFAULT_STALE_TIME = 3000; // 3 seconds
16
15
  const INITIAL_QUERY_STATE = {
17
16
  isWaiting: false,
18
17
  isWaitingNextPage: false,
@@ -44,11 +43,13 @@ const useQueryDefaultDeps = (state) => [
44
43
  state.hasNextPage,
45
44
  ];
46
45
  const createQuery = (queryFn, options = {}) => {
47
- const { onFirstSubscribe = utils_1.noop, onSubscribe = utils_1.noop, onLastUnsubscribe = utils_1.noop, onBeforeChangeKey = utils_1.noop, defaultDeps = useQueryDefaultDeps, select = utils_1.identityFn, staleTime = DEFAULT_STALE_TIME, fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, keepPreviousData, getNextPageParam = () => undefined, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, hashKeyFn = utils_1.hashStoreKey, ...createStoresOptions } = options;
46
+ const { onFirstSubscribe = utils_1.noop, onSubscribe = utils_1.noop, onLastUnsubscribe = utils_1.noop, onBeforeChangeKey = utils_1.noop, defaultDeps = useQueryDefaultDeps, select = utils_1.identityFn, staleTime = 3000, // 3 seconds
47
+ fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, // 3 seconds
48
+ keepPreviousData, getNextPageParam = () => undefined, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, ...createStoresOptions } = options;
48
49
  const retryTimeoutId = new Map();
49
50
  const retryNextPageTimeoutId = new Map();
50
51
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
51
- const useQuery = (0, create_stores_1.createStores)(({ key: _key, get, set }) => {
52
+ const useQuery = (0, create_stores_1.createStores)(({ get, set, key: _key, keyHash }) => {
52
53
  const key = _key;
53
54
  const getRetryProps = (error, retryCount) => {
54
55
  const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
@@ -73,13 +74,13 @@ const createQuery = (queryFn, options = {}) => {
73
74
  set({ isGoingToRetry: false, isWaiting: true });
74
75
  else
75
76
  set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
76
- clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
77
+ clearTimeout(retryTimeoutId.get(keyHash));
77
78
  }
78
79
  const stateBeforeCallQuery = { ...get(), pageParam };
79
- preventReplaceResponse.set(hashKeyFn(key), false);
80
+ preventReplaceResponse.set(keyHash, false);
80
81
  queryFn(key, stateBeforeCallQuery)
81
82
  .then((response) => {
82
- if (preventReplaceResponse.get(hashKeyFn(key))) {
83
+ if (preventReplaceResponse.get(keyHash)) {
83
84
  set({ isWaiting: false });
84
85
  return;
85
86
  }
@@ -147,7 +148,7 @@ const createQuery = (queryFn, options = {}) => {
147
148
  hasNextPage: pageParam !== undefined,
148
149
  });
149
150
  if (shouldRetry) {
150
- retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
151
+ retryTimeoutId.set(keyHash, window.setTimeout(() => {
151
152
  set({ retryCount: prevState.retryCount + 1 });
152
153
  callQuery();
153
154
  }, delay));
@@ -175,7 +176,7 @@ const createQuery = (queryFn, options = {}) => {
175
176
  if (isWaitingNextPage || !hasNextPage)
176
177
  return;
177
178
  set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
178
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
179
+ clearTimeout(retryNextPageTimeoutId.get(keyHash));
179
180
  queryFn(key, { ...state, pageParam })
180
181
  .then((response) => {
181
182
  const newPageParam = getNextPageParam(response, pageParams.length);
@@ -200,7 +201,7 @@ const createQuery = (queryFn, options = {}) => {
200
201
  isGoingToRetryNextPage: shouldRetry,
201
202
  });
202
203
  if (shouldRetry) {
203
- retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
204
+ retryNextPageTimeoutId.set(keyHash, window.setTimeout(() => {
204
205
  set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
205
206
  fetchNextPage();
206
207
  }, delay));
@@ -210,6 +211,7 @@ const createQuery = (queryFn, options = {}) => {
210
211
  return {
211
212
  ...INITIAL_QUERY_STATE,
212
213
  key,
214
+ keyHash,
213
215
  fetch,
214
216
  forceFetch,
215
217
  fetchNextPage,
@@ -228,7 +230,6 @@ const createQuery = (queryFn, options = {}) => {
228
230
  return {
229
231
  ...createStoresOptions,
230
232
  defaultDeps,
231
- hashKeyFn,
232
233
  onFirstSubscribe: (state) => {
233
234
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
234
235
  window.addEventListener('focus', fetchWindowFocusHandler);
@@ -247,8 +248,8 @@ const createQuery = (queryFn, options = {}) => {
247
248
  window.removeEventListener('focus', fetchWindowFocusHandler);
248
249
  }
249
250
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
250
- clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
251
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
251
+ clearTimeout(retryTimeoutId.get(state.keyHash));
252
+ clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
252
253
  onLastUnsubscribe(state);
253
254
  },
254
255
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -326,7 +327,7 @@ const createQuery = (queryFn, options = {}) => {
326
327
  response: optimisticResponse,
327
328
  data: select(optimisticResponse, { key: key, data: null }),
328
329
  });
329
- preventReplaceResponse.set(hashKeyFn(key), true);
330
+ preventReplaceResponse.set(prevState.keyHash, true);
330
331
  const revert = () => {
331
332
  useQuery.set(key, {
332
333
  isOptimisticData: false,
@@ -3,9 +3,10 @@ import { WatchProps } from './create-store';
3
3
  type Maybe<T> = T | null | undefined;
4
4
  export type StoreKey = Record<string, any> | undefined;
5
5
  export type StoresInitializer<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = (api: {
6
- key: TKey;
7
6
  get: () => T;
8
7
  set: (value: SetStoreData<T>, silent?: boolean) => void;
8
+ key: TKey;
9
+ keyHash: string;
9
10
  }) => T;
10
11
  export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = {
11
12
  /**
@@ -9,11 +9,11 @@ const createStores = (initializer, options = {}) => {
9
9
  const stores = new Map();
10
10
  const getStore = (_key) => {
11
11
  const key = _key || {};
12
- const normalizedKey = hashKeyFn(key);
13
- if (!stores.has(normalizedKey)) {
14
- stores.set(normalizedKey, (0, vanilla_1.initStore)((api) => initializer({ key, ...api }), options));
12
+ const keyHash = hashKeyFn(key);
13
+ if (!stores.has(keyHash)) {
14
+ stores.set(keyHash, (0, vanilla_1.initStore)((api) => initializer({ key, keyHash, ...api }), options));
15
15
  }
16
- return stores.get(normalizedKey);
16
+ return stores.get(keyHash);
17
17
  };
18
18
  /**
19
19
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -21,9 +21,9 @@ const createStores = (initializer, options = {}) => {
21
21
  const useStores = (...args) => {
22
22
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
23
23
  const key = _key || {};
24
- const normalizedKey = hashKeyFn(key);
24
+ const keyHash = hashKeyFn(key);
25
25
  // eslint-disable-next-line react-hooks/exhaustive-deps
26
- const { get, subscribe } = (0, hooks_1.useMemo)(() => getStore(key), [normalizedKey]);
26
+ const { get, subscribe } = (0, hooks_1.useMemo)(() => getStore(key), [keyHash]);
27
27
  const [state, setState] = (0, hooks_1.useState)(get);
28
28
  const isFirstRender = (0, hooks_1.useRef)(true);
29
29
  const prevKey = (0, hooks_1.useRef)(key);
@@ -37,7 +37,7 @@ const createStores = (initializer, options = {}) => {
37
37
  const unsubs = subscribe(setState, selectDeps);
38
38
  return unsubs;
39
39
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, [normalizedKey]);
40
+ }, [keyHash]);
41
41
  return state;
42
42
  };
43
43
  useStores.get = (key) => {
@@ -4,6 +4,10 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
4
4
  * Query store key, an object that will be hashed into a string as a query store identifier.
5
5
  */
6
6
  key: TKey;
7
+ /**
8
+ * Query store key hash, a string used as a query store identifier.
9
+ */
10
+ keyHash: string;
7
11
  /**
8
12
  * Will only be called if the data is stale or empty.
9
13
  */
@@ -12,7 +12,6 @@ const getDecision = (value, param, { ifTrue, ifAlways }) => {
12
12
  ifAlways();
13
13
  }
14
14
  };
15
- const DEFAULT_STALE_TIME = 3000; // 3 seconds
16
15
  const INITIAL_QUERY_STATE = {
17
16
  isWaiting: false,
18
17
  isWaitingNextPage: false,
@@ -44,11 +43,13 @@ const useQueryDefaultDeps = (state) => [
44
43
  state.hasNextPage,
45
44
  ];
46
45
  const createQuery = (queryFn, options = {}) => {
47
- const { onFirstSubscribe = utils_1.noop, onSubscribe = utils_1.noop, onLastUnsubscribe = utils_1.noop, onBeforeChangeKey = utils_1.noop, defaultDeps = useQueryDefaultDeps, select = utils_1.identityFn, staleTime = DEFAULT_STALE_TIME, fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, keepPreviousData, getNextPageParam = () => undefined, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, hashKeyFn = utils_1.hashStoreKey, ...createStoresOptions } = options;
46
+ const { onFirstSubscribe = utils_1.noop, onSubscribe = utils_1.noop, onLastUnsubscribe = utils_1.noop, onBeforeChangeKey = utils_1.noop, defaultDeps = useQueryDefaultDeps, select = utils_1.identityFn, staleTime = 3000, // 3 seconds
47
+ fetchOnMount = true, fetchOnWindowFocus = true, enabled = true, retry = 1, retryDelay = 3000, // 3 seconds
48
+ keepPreviousData, getNextPageParam = () => undefined, onSuccess = utils_1.noop, onError = utils_1.noop, onSettled = utils_1.noop, ...createStoresOptions } = options;
48
49
  const retryTimeoutId = new Map();
49
50
  const retryNextPageTimeoutId = new Map();
50
51
  const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
51
- const useQuery = (0, create_stores_1.createStores)(({ key: _key, get, set }) => {
52
+ const useQuery = (0, create_stores_1.createStores)(({ get, set, key: _key, keyHash }) => {
52
53
  const key = _key;
53
54
  const getRetryProps = (error, retryCount) => {
54
55
  const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
@@ -73,13 +74,13 @@ const createQuery = (queryFn, options = {}) => {
73
74
  set({ isGoingToRetry: false, isWaiting: true });
74
75
  else
75
76
  set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
76
- clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
77
+ clearTimeout(retryTimeoutId.get(keyHash));
77
78
  }
78
79
  const stateBeforeCallQuery = { ...get(), pageParam };
79
- preventReplaceResponse.set(hashKeyFn(key), false);
80
+ preventReplaceResponse.set(keyHash, false);
80
81
  queryFn(key, stateBeforeCallQuery)
81
82
  .then((response) => {
82
- if (preventReplaceResponse.get(hashKeyFn(key))) {
83
+ if (preventReplaceResponse.get(keyHash)) {
83
84
  set({ isWaiting: false });
84
85
  return;
85
86
  }
@@ -147,7 +148,7 @@ const createQuery = (queryFn, options = {}) => {
147
148
  hasNextPage: pageParam !== undefined,
148
149
  });
149
150
  if (shouldRetry) {
150
- retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
151
+ retryTimeoutId.set(keyHash, window.setTimeout(() => {
151
152
  set({ retryCount: prevState.retryCount + 1 });
152
153
  callQuery();
153
154
  }, delay));
@@ -175,7 +176,7 @@ const createQuery = (queryFn, options = {}) => {
175
176
  if (isWaitingNextPage || !hasNextPage)
176
177
  return;
177
178
  set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
178
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
179
+ clearTimeout(retryNextPageTimeoutId.get(keyHash));
179
180
  queryFn(key, { ...state, pageParam })
180
181
  .then((response) => {
181
182
  const newPageParam = getNextPageParam(response, pageParams.length);
@@ -200,7 +201,7 @@ const createQuery = (queryFn, options = {}) => {
200
201
  isGoingToRetryNextPage: shouldRetry,
201
202
  });
202
203
  if (shouldRetry) {
203
- retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
204
+ retryNextPageTimeoutId.set(keyHash, window.setTimeout(() => {
204
205
  set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
205
206
  fetchNextPage();
206
207
  }, delay));
@@ -210,6 +211,7 @@ const createQuery = (queryFn, options = {}) => {
210
211
  return {
211
212
  ...INITIAL_QUERY_STATE,
212
213
  key,
214
+ keyHash,
213
215
  fetch,
214
216
  forceFetch,
215
217
  fetchNextPage,
@@ -228,7 +230,6 @@ const createQuery = (queryFn, options = {}) => {
228
230
  return {
229
231
  ...createStoresOptions,
230
232
  defaultDeps,
231
- hashKeyFn,
232
233
  onFirstSubscribe: (state) => {
233
234
  if (typeof window !== 'undefined' && fetchOnWindowFocus) {
234
235
  window.addEventListener('focus', fetchWindowFocusHandler);
@@ -247,8 +248,8 @@ const createQuery = (queryFn, options = {}) => {
247
248
  window.removeEventListener('focus', fetchWindowFocusHandler);
248
249
  }
249
250
  useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
250
- clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
251
- clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
251
+ clearTimeout(retryTimeoutId.get(state.keyHash));
252
+ clearTimeout(retryNextPageTimeoutId.get(state.keyHash));
252
253
  onLastUnsubscribe(state);
253
254
  },
254
255
  onBeforeChangeKey: (nextKey, prevKey) => {
@@ -326,7 +327,7 @@ const createQuery = (queryFn, options = {}) => {
326
327
  response: optimisticResponse,
327
328
  data: select(optimisticResponse, { key: key, data: null }),
328
329
  });
329
- preventReplaceResponse.set(hashKeyFn(key), true);
330
+ preventReplaceResponse.set(prevState.keyHash, true);
330
331
  const revert = () => {
331
332
  useQuery.set(key, {
332
333
  isOptimisticData: false,
@@ -3,9 +3,10 @@ import { WatchProps } from './create-store';
3
3
  type Maybe<T> = T | null | undefined;
4
4
  export type StoreKey = Record<string, any> | undefined;
5
5
  export type StoresInitializer<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = (api: {
6
- key: TKey;
7
6
  get: () => T;
8
7
  set: (value: SetStoreData<T>, silent?: boolean) => void;
8
+ key: TKey;
9
+ keyHash: string;
9
10
  }) => T;
10
11
  export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = {
11
12
  /**
@@ -9,11 +9,11 @@ const createStores = (initializer, options = {}) => {
9
9
  const stores = new Map();
10
10
  const getStore = (_key) => {
11
11
  const key = _key || {};
12
- const normalizedKey = hashKeyFn(key);
13
- if (!stores.has(normalizedKey)) {
14
- stores.set(normalizedKey, (0, vanilla_1.initStore)((api) => initializer({ key, ...api }), options));
12
+ const keyHash = hashKeyFn(key);
13
+ if (!stores.has(keyHash)) {
14
+ stores.set(keyHash, (0, vanilla_1.initStore)((api) => initializer({ key, keyHash, ...api }), options));
15
15
  }
16
- return stores.get(normalizedKey);
16
+ return stores.get(keyHash);
17
17
  };
18
18
  /**
19
19
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -21,9 +21,9 @@ const createStores = (initializer, options = {}) => {
21
21
  const useStores = (...args) => {
22
22
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
23
23
  const key = _key || {};
24
- const normalizedKey = hashKeyFn(key);
24
+ const keyHash = hashKeyFn(key);
25
25
  // eslint-disable-next-line react-hooks/exhaustive-deps
26
- const { get, subscribe } = (0, react_1.useMemo)(() => getStore(key), [normalizedKey]);
26
+ const { get, subscribe } = (0, react_1.useMemo)(() => getStore(key), [keyHash]);
27
27
  const [state, setState] = (0, react_1.useState)(get);
28
28
  const isFirstRender = (0, react_1.useRef)(true);
29
29
  const prevKey = (0, react_1.useRef)(key);
@@ -37,7 +37,7 @@ const createStores = (initializer, options = {}) => {
37
37
  const unsubs = subscribe(setState, selectDeps);
38
38
  return unsubs;
39
39
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, [normalizedKey]);
40
+ }, [keyHash]);
41
41
  return state;
42
42
  };
43
43
  useStores.get = (key) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "floppy-disk",
3
- "version": "2.0.2-beta.1",
3
+ "version": "2.1.0-beta.1",
4
4
  "description": "FloppyDisk - lightweight, simple, and powerful state management library",
5
5
  "keywords": [
6
6
  "state",