floppy-disk 2.0.2 → 2.1.0-beta.2

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/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 { hasValue, 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,20 +71,20 @@ 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
  }
83
84
  responseAllPages.push(response);
84
85
  const newPageParam = getNextPageParam(response, responseAllPages.length);
85
86
  newPageParams.push(newPageParam);
86
- if (newPageParam !== undefined && newPageParams.length < pageParams.length) {
87
+ if (hasValue(newPageParam) && newPageParams.length < pageParams.length) {
87
88
  pageParam = newPageParam;
88
89
  callQuery();
89
90
  return;
@@ -108,7 +109,7 @@ export const createQuery = (queryFn, options = {}) => {
108
109
  retryCount: 0,
109
110
  pageParam: newPageParam,
110
111
  pageParams: newPageParams,
111
- hasNextPage: newPageParam !== undefined,
112
+ hasNextPage: hasValue(newPageParam),
112
113
  });
113
114
  onSuccess(response, stateBeforeCallQuery);
114
115
  })
@@ -130,7 +131,7 @@ export const createQuery = (queryFn, options = {}) => {
130
131
  errorUpdatedAt,
131
132
  isGoingToRetry: shouldRetry,
132
133
  pageParam,
133
- hasNextPage: pageParam !== undefined,
134
+ hasNextPage: hasValue(pageParam),
134
135
  }
135
136
  : {
136
137
  isWaiting: false,
@@ -141,10 +142,10 @@ export const createQuery = (queryFn, options = {}) => {
141
142
  errorUpdatedAt,
142
143
  isGoingToRetry: shouldRetry,
143
144
  pageParam,
144
- hasNextPage: pageParam !== undefined,
145
+ hasNextPage: hasValue(pageParam),
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);
@@ -183,7 +184,7 @@ export const createQuery = (queryFn, options = {}) => {
183
184
  data: select(response, { key, data }),
184
185
  pageParam: newPageParam,
185
186
  pageParams: pageParams.concat(newPageParam),
186
- hasNextPage: newPageParam !== undefined,
187
+ hasNextPage: hasValue(newPageParam),
187
188
  });
188
189
  })
189
190
  .catch((error) => {
@@ -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) => {
@@ -286,7 +287,7 @@ export const createQuery = (queryFn, options = {}) => {
286
287
  data: select(response, { key: key, data: null }),
287
288
  pageParam: newPageParam,
288
289
  pageParams: [undefined, newPageParam],
289
- hasNextPage: newPageParam !== undefined,
290
+ hasNextPage: hasValue(newPageParam),
290
291
  });
291
292
  });
292
293
  };
@@ -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
  /**
@@ -38,6 +39,10 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
38
39
  };
39
40
  export type CreateStoresOptions<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = InitStoreOptions<T> & {
40
41
  onBeforeChangeKey?: (nextKey: TKey, prevKey: TKey) => void;
42
+ /**
43
+ * Will be triggered when a single store with a specific key was initialized.
44
+ */
45
+ onStoreInitialized?: (key: TKey, keyHash: string) => void;
41
46
  defaultDeps?: SelectDeps<T>;
42
47
  hashKeyFn?: (obj: TKey) => string;
43
48
  };
@@ -2,15 +2,16 @@ import { useEffect, useMemo, useRef, useState } from 'preact/hooks';
2
2
  import { hashStoreKey, noop } from '../utils';
3
3
  import { initStore, } from '../vanilla';
4
4
  export const createStores = (initializer, options = {}) => {
5
- const { onBeforeChangeKey = noop, defaultDeps, hashKeyFn = hashStoreKey } = options;
5
+ const { onBeforeChangeKey = noop, onStoreInitialized = noop, defaultDeps, hashKeyFn = hashStoreKey, } = 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
+ onStoreInitialized(key, keyHash);
12
13
  }
13
- return stores.get(normalizedKey);
14
+ return stores.get(keyHash);
14
15
  };
15
16
  /**
16
17
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -18,9 +19,9 @@ export const createStores = (initializer, options = {}) => {
18
19
  const useStores = (...args) => {
19
20
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
20
21
  const key = _key || {};
21
- const normalizedKey = hashKeyFn(key);
22
+ const keyHash = hashKeyFn(key);
22
23
  // eslint-disable-next-line react-hooks/exhaustive-deps
23
- const { get, subscribe } = useMemo(() => getStore(key), [normalizedKey]);
24
+ const { get, subscribe } = useMemo(() => getStore(key), [keyHash]);
24
25
  const [state, setState] = useState(get);
25
26
  const isFirstRender = useRef(true);
26
27
  const prevKey = useRef(key);
@@ -34,7 +35,7 @@ export const createStores = (initializer, options = {}) => {
34
35
  const unsubs = subscribe(setState, selectDeps);
35
36
  return unsubs;
36
37
  // eslint-disable-next-line react-hooks/exhaustive-deps
37
- }, [normalizedKey]);
38
+ }, [keyHash]);
38
39
  return state;
39
40
  };
40
41
  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 { hasValue, 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,20 +71,20 @@ 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
  }
83
84
  responseAllPages.push(response);
84
85
  const newPageParam = getNextPageParam(response, responseAllPages.length);
85
86
  newPageParams.push(newPageParam);
86
- if (newPageParam !== undefined && newPageParams.length < pageParams.length) {
87
+ if (hasValue(newPageParam) && newPageParams.length < pageParams.length) {
87
88
  pageParam = newPageParam;
88
89
  callQuery();
89
90
  return;
@@ -108,7 +109,7 @@ export const createQuery = (queryFn, options = {}) => {
108
109
  retryCount: 0,
109
110
  pageParam: newPageParam,
110
111
  pageParams: newPageParams,
111
- hasNextPage: newPageParam !== undefined,
112
+ hasNextPage: hasValue(newPageParam),
112
113
  });
113
114
  onSuccess(response, stateBeforeCallQuery);
114
115
  })
@@ -130,7 +131,7 @@ export const createQuery = (queryFn, options = {}) => {
130
131
  errorUpdatedAt,
131
132
  isGoingToRetry: shouldRetry,
132
133
  pageParam,
133
- hasNextPage: pageParam !== undefined,
134
+ hasNextPage: hasValue(pageParam),
134
135
  }
135
136
  : {
136
137
  isWaiting: false,
@@ -141,10 +142,10 @@ export const createQuery = (queryFn, options = {}) => {
141
142
  errorUpdatedAt,
142
143
  isGoingToRetry: shouldRetry,
143
144
  pageParam,
144
- hasNextPage: pageParam !== undefined,
145
+ hasNextPage: hasValue(pageParam),
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);
@@ -183,7 +184,7 @@ export const createQuery = (queryFn, options = {}) => {
183
184
  data: select(response, { key, data }),
184
185
  pageParam: newPageParam,
185
186
  pageParams: pageParams.concat(newPageParam),
186
- hasNextPage: newPageParam !== undefined,
187
+ hasNextPage: hasValue(newPageParam),
187
188
  });
188
189
  })
189
190
  .catch((error) => {
@@ -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) => {
@@ -286,7 +287,7 @@ export const createQuery = (queryFn, options = {}) => {
286
287
  data: select(response, { key: key, data: null }),
287
288
  pageParam: newPageParam,
288
289
  pageParams: [undefined, newPageParam],
289
- hasNextPage: newPageParam !== undefined,
290
+ hasNextPage: hasValue(newPageParam),
290
291
  });
291
292
  });
292
293
  };
@@ -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
  /**
@@ -38,6 +39,10 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
38
39
  };
39
40
  export type CreateStoresOptions<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = InitStoreOptions<T> & {
40
41
  onBeforeChangeKey?: (nextKey: TKey, prevKey: TKey) => void;
42
+ /**
43
+ * Will be triggered when a single store with a specific key was initialized.
44
+ */
45
+ onStoreInitialized?: (key: TKey, keyHash: string) => void;
41
46
  defaultDeps?: SelectDeps<T>;
42
47
  hashKeyFn?: (obj: TKey) => string;
43
48
  };
@@ -2,15 +2,16 @@ import { useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { hashStoreKey, noop } from '../utils';
3
3
  import { initStore, } from '../vanilla';
4
4
  export const createStores = (initializer, options = {}) => {
5
- const { onBeforeChangeKey = noop, defaultDeps, hashKeyFn = hashStoreKey } = options;
5
+ const { onBeforeChangeKey = noop, onStoreInitialized = noop, defaultDeps, hashKeyFn = hashStoreKey, } = 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
+ onStoreInitialized(key, keyHash);
12
13
  }
13
- return stores.get(normalizedKey);
14
+ return stores.get(keyHash);
14
15
  };
15
16
  /**
16
17
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -18,9 +19,9 @@ export const createStores = (initializer, options = {}) => {
18
19
  const useStores = (...args) => {
19
20
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
20
21
  const key = _key || {};
21
- const normalizedKey = hashKeyFn(key);
22
+ const keyHash = hashKeyFn(key);
22
23
  // eslint-disable-next-line react-hooks/exhaustive-deps
23
- const { get, subscribe } = useMemo(() => getStore(key), [normalizedKey]);
24
+ const { get, subscribe } = useMemo(() => getStore(key), [keyHash]);
24
25
  const [state, setState] = useState(get);
25
26
  const isFirstRender = useRef(true);
26
27
  const prevKey = useRef(key);
@@ -34,7 +35,7 @@ export const createStores = (initializer, options = {}) => {
34
35
  const unsubs = subscribe(setState, selectDeps);
35
36
  return unsubs;
36
37
  // eslint-disable-next-line react-hooks/exhaustive-deps
37
- }, [normalizedKey]);
38
+ }, [keyHash]);
38
39
  return state;
39
40
  };
40
41
  useStores.get = (key) => {
@@ -1,3 +1,4 @@
1
1
  export declare const noop: () => void;
2
- export declare const identityFn: <T>(a: T) => T;
2
+ export declare const identityFn: <T>(value: T) => T;
3
+ export declare const hasValue: (value: any) => boolean;
3
4
  export declare const hashStoreKey: (obj?: any) => string;
@@ -1,3 +1,4 @@
1
1
  export const noop = () => { };
2
- export const identityFn = (a) => a;
2
+ export const identityFn = (value) => value;
3
+ export const hasValue = (value) => value !== undefined && value !== null;
3
4
  export const hashStoreKey = (obj) => JSON.stringify(obj, Object.keys(obj).sort());
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,20 +74,20 @@ 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
  }
86
87
  responseAllPages.push(response);
87
88
  const newPageParam = getNextPageParam(response, responseAllPages.length);
88
89
  newPageParams.push(newPageParam);
89
- if (newPageParam !== undefined && newPageParams.length < pageParams.length) {
90
+ if ((0, utils_1.hasValue)(newPageParam) && newPageParams.length < pageParams.length) {
90
91
  pageParam = newPageParam;
91
92
  callQuery();
92
93
  return;
@@ -111,7 +112,7 @@ const createQuery = (queryFn, options = {}) => {
111
112
  retryCount: 0,
112
113
  pageParam: newPageParam,
113
114
  pageParams: newPageParams,
114
- hasNextPage: newPageParam !== undefined,
115
+ hasNextPage: (0, utils_1.hasValue)(newPageParam),
115
116
  });
116
117
  onSuccess(response, stateBeforeCallQuery);
117
118
  })
@@ -133,7 +134,7 @@ const createQuery = (queryFn, options = {}) => {
133
134
  errorUpdatedAt,
134
135
  isGoingToRetry: shouldRetry,
135
136
  pageParam,
136
- hasNextPage: pageParam !== undefined,
137
+ hasNextPage: (0, utils_1.hasValue)(pageParam),
137
138
  }
138
139
  : {
139
140
  isWaiting: false,
@@ -144,10 +145,10 @@ const createQuery = (queryFn, options = {}) => {
144
145
  errorUpdatedAt,
145
146
  isGoingToRetry: shouldRetry,
146
147
  pageParam,
147
- hasNextPage: pageParam !== undefined,
148
+ hasNextPage: (0, utils_1.hasValue)(pageParam),
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);
@@ -186,7 +187,7 @@ const createQuery = (queryFn, options = {}) => {
186
187
  data: select(response, { key, data }),
187
188
  pageParam: newPageParam,
188
189
  pageParams: pageParams.concat(newPageParam),
189
- hasNextPage: newPageParam !== undefined,
190
+ hasNextPage: (0, utils_1.hasValue)(newPageParam),
190
191
  });
191
192
  })
192
193
  .catch((error) => {
@@ -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) => {
@@ -289,7 +290,7 @@ const createQuery = (queryFn, options = {}) => {
289
290
  data: select(response, { key: key, data: null }),
290
291
  pageParam: newPageParam,
291
292
  pageParams: [undefined, newPageParam],
292
- hasNextPage: newPageParam !== undefined,
293
+ hasNextPage: (0, utils_1.hasValue)(newPageParam),
293
294
  });
294
295
  });
295
296
  };
@@ -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
  /**
@@ -38,6 +39,10 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
38
39
  };
39
40
  export type CreateStoresOptions<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = InitStoreOptions<T> & {
40
41
  onBeforeChangeKey?: (nextKey: TKey, prevKey: TKey) => void;
42
+ /**
43
+ * Will be triggered when a single store with a specific key was initialized.
44
+ */
45
+ onStoreInitialized?: (key: TKey, keyHash: string) => void;
41
46
  defaultDeps?: SelectDeps<T>;
42
47
  hashKeyFn?: (obj: TKey) => string;
43
48
  };
@@ -5,15 +5,16 @@ const hooks_1 = require("preact/hooks");
5
5
  const utils_1 = require("../utils");
6
6
  const vanilla_1 = require("../vanilla");
7
7
  const createStores = (initializer, options = {}) => {
8
- const { onBeforeChangeKey = utils_1.noop, defaultDeps, hashKeyFn = utils_1.hashStoreKey } = options;
8
+ const { onBeforeChangeKey = utils_1.noop, onStoreInitialized = utils_1.noop, defaultDeps, hashKeyFn = utils_1.hashStoreKey, } = 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
+ onStoreInitialized(key, keyHash);
15
16
  }
16
- return stores.get(normalizedKey);
17
+ return stores.get(keyHash);
17
18
  };
18
19
  /**
19
20
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -21,9 +22,9 @@ const createStores = (initializer, options = {}) => {
21
22
  const useStores = (...args) => {
22
23
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
23
24
  const key = _key || {};
24
- const normalizedKey = hashKeyFn(key);
25
+ const keyHash = hashKeyFn(key);
25
26
  // eslint-disable-next-line react-hooks/exhaustive-deps
26
- const { get, subscribe } = (0, hooks_1.useMemo)(() => getStore(key), [normalizedKey]);
27
+ const { get, subscribe } = (0, hooks_1.useMemo)(() => getStore(key), [keyHash]);
27
28
  const [state, setState] = (0, hooks_1.useState)(get);
28
29
  const isFirstRender = (0, hooks_1.useRef)(true);
29
30
  const prevKey = (0, hooks_1.useRef)(key);
@@ -37,7 +38,7 @@ const createStores = (initializer, options = {}) => {
37
38
  const unsubs = subscribe(setState, selectDeps);
38
39
  return unsubs;
39
40
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, [normalizedKey]);
41
+ }, [keyHash]);
41
42
  return state;
42
43
  };
43
44
  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,20 +74,20 @@ 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
  }
86
87
  responseAllPages.push(response);
87
88
  const newPageParam = getNextPageParam(response, responseAllPages.length);
88
89
  newPageParams.push(newPageParam);
89
- if (newPageParam !== undefined && newPageParams.length < pageParams.length) {
90
+ if ((0, utils_1.hasValue)(newPageParam) && newPageParams.length < pageParams.length) {
90
91
  pageParam = newPageParam;
91
92
  callQuery();
92
93
  return;
@@ -111,7 +112,7 @@ const createQuery = (queryFn, options = {}) => {
111
112
  retryCount: 0,
112
113
  pageParam: newPageParam,
113
114
  pageParams: newPageParams,
114
- hasNextPage: newPageParam !== undefined,
115
+ hasNextPage: (0, utils_1.hasValue)(newPageParam),
115
116
  });
116
117
  onSuccess(response, stateBeforeCallQuery);
117
118
  })
@@ -133,7 +134,7 @@ const createQuery = (queryFn, options = {}) => {
133
134
  errorUpdatedAt,
134
135
  isGoingToRetry: shouldRetry,
135
136
  pageParam,
136
- hasNextPage: pageParam !== undefined,
137
+ hasNextPage: (0, utils_1.hasValue)(pageParam),
137
138
  }
138
139
  : {
139
140
  isWaiting: false,
@@ -144,10 +145,10 @@ const createQuery = (queryFn, options = {}) => {
144
145
  errorUpdatedAt,
145
146
  isGoingToRetry: shouldRetry,
146
147
  pageParam,
147
- hasNextPage: pageParam !== undefined,
148
+ hasNextPage: (0, utils_1.hasValue)(pageParam),
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);
@@ -186,7 +187,7 @@ const createQuery = (queryFn, options = {}) => {
186
187
  data: select(response, { key, data }),
187
188
  pageParam: newPageParam,
188
189
  pageParams: pageParams.concat(newPageParam),
189
- hasNextPage: newPageParam !== undefined,
190
+ hasNextPage: (0, utils_1.hasValue)(newPageParam),
190
191
  });
191
192
  })
192
193
  .catch((error) => {
@@ -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) => {
@@ -289,7 +290,7 @@ const createQuery = (queryFn, options = {}) => {
289
290
  data: select(response, { key: key, data: null }),
290
291
  pageParam: newPageParam,
291
292
  pageParams: [undefined, newPageParam],
292
- hasNextPage: newPageParam !== undefined,
293
+ hasNextPage: (0, utils_1.hasValue)(newPageParam),
293
294
  });
294
295
  });
295
296
  };
@@ -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
  /**
@@ -38,6 +39,10 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
38
39
  };
39
40
  export type CreateStoresOptions<TKey extends StoreKey = StoreKey, T extends StoreData = StoreData> = InitStoreOptions<T> & {
40
41
  onBeforeChangeKey?: (nextKey: TKey, prevKey: TKey) => void;
42
+ /**
43
+ * Will be triggered when a single store with a specific key was initialized.
44
+ */
45
+ onStoreInitialized?: (key: TKey, keyHash: string) => void;
41
46
  defaultDeps?: SelectDeps<T>;
42
47
  hashKeyFn?: (obj: TKey) => string;
43
48
  };
@@ -5,15 +5,16 @@ const react_1 = require("react");
5
5
  const utils_1 = require("../utils");
6
6
  const vanilla_1 = require("../vanilla");
7
7
  const createStores = (initializer, options = {}) => {
8
- const { onBeforeChangeKey = utils_1.noop, defaultDeps, hashKeyFn = utils_1.hashStoreKey } = options;
8
+ const { onBeforeChangeKey = utils_1.noop, onStoreInitialized = utils_1.noop, defaultDeps, hashKeyFn = utils_1.hashStoreKey, } = 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
+ onStoreInitialized(key, keyHash);
15
16
  }
16
- return stores.get(normalizedKey);
17
+ return stores.get(keyHash);
17
18
  };
18
19
  /**
19
20
  * IMPORTANT NOTE: selectDeps function must not be changed after initialization.
@@ -21,9 +22,9 @@ const createStores = (initializer, options = {}) => {
21
22
  const useStores = (...args) => {
22
23
  const [_key, selectDeps = defaultDeps] = (typeof args[0] === 'function' ? [{}, args[0]] : args);
23
24
  const key = _key || {};
24
- const normalizedKey = hashKeyFn(key);
25
+ const keyHash = hashKeyFn(key);
25
26
  // eslint-disable-next-line react-hooks/exhaustive-deps
26
- const { get, subscribe } = (0, react_1.useMemo)(() => getStore(key), [normalizedKey]);
27
+ const { get, subscribe } = (0, react_1.useMemo)(() => getStore(key), [keyHash]);
27
28
  const [state, setState] = (0, react_1.useState)(get);
28
29
  const isFirstRender = (0, react_1.useRef)(true);
29
30
  const prevKey = (0, react_1.useRef)(key);
@@ -37,7 +38,7 @@ const createStores = (initializer, options = {}) => {
37
38
  const unsubs = subscribe(setState, selectDeps);
38
39
  return unsubs;
39
40
  // eslint-disable-next-line react-hooks/exhaustive-deps
40
- }, [normalizedKey]);
41
+ }, [keyHash]);
41
42
  return state;
42
43
  };
43
44
  useStores.get = (key) => {
@@ -1,3 +1,4 @@
1
1
  export declare const noop: () => void;
2
- export declare const identityFn: <T>(a: T) => T;
2
+ export declare const identityFn: <T>(value: T) => T;
3
+ export declare const hasValue: (value: any) => boolean;
3
4
  export declare const hashStoreKey: (obj?: any) => string;
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hashStoreKey = exports.identityFn = exports.noop = void 0;
3
+ exports.hashStoreKey = exports.hasValue = exports.identityFn = exports.noop = void 0;
4
4
  const noop = () => { };
5
5
  exports.noop = noop;
6
- const identityFn = (a) => a;
6
+ const identityFn = (value) => value;
7
7
  exports.identityFn = identityFn;
8
+ const hasValue = (value) => value !== undefined && value !== null;
9
+ exports.hasValue = hasValue;
8
10
  const hashStoreKey = (obj) => JSON.stringify(obj, Object.keys(obj).sort());
9
11
  exports.hashStoreKey = hashStoreKey;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "floppy-disk",
3
- "version": "2.0.2",
3
+ "version": "2.1.0-beta.2",
4
4
  "description": "FloppyDisk - lightweight, simple, and powerful state management library",
5
5
  "keywords": [
6
6
  "state",