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 +1 -0
- package/esm/index.js +1 -0
- package/esm/preact/create-query.d.ts +4 -0
- package/esm/preact/create-query.js +21 -20
- package/esm/preact/create-stores.d.ts +6 -1
- package/esm/preact/create-stores.js +9 -8
- package/esm/react/create-query.d.ts +4 -0
- package/esm/react/create-query.js +21 -20
- package/esm/react/create-stores.d.ts +6 -1
- package/esm/react/create-stores.js +9 -8
- package/esm/utils/index.d.ts +2 -1
- package/esm/utils/index.js +2 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -0
- package/lib/preact/create-query.d.ts +4 -0
- package/lib/preact/create-query.js +20 -19
- package/lib/preact/create-stores.d.ts +6 -1
- package/lib/preact/create-stores.js +9 -8
- package/lib/react/create-query.d.ts +4 -0
- package/lib/react/create-query.js +20 -19
- package/lib/react/create-stores.d.ts +6 -1
- package/lib/react/create-stores.js +9 -8
- package/lib/utils/index.d.ts +2 -1
- package/lib/utils/index.js +4 -2
- package/package.json +1 -1
package/esm/index.d.ts
CHANGED
package/esm/index.js
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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,
|
|
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(
|
|
74
|
+
clearTimeout(retryTimeoutId.get(keyHash));
|
|
74
75
|
}
|
|
75
76
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
76
|
-
preventReplaceResponse.set(
|
|
77
|
+
preventReplaceResponse.set(keyHash, false);
|
|
77
78
|
queryFn(key, stateBeforeCallQuery)
|
|
78
79
|
.then((response) => {
|
|
79
|
-
if (preventReplaceResponse.get(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
145
|
+
hasNextPage: hasValue(pageParam),
|
|
145
146
|
});
|
|
146
147
|
if (shouldRetry) {
|
|
147
|
-
retryTimeoutId.set(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
248
|
-
clearTimeout(retryNextPageTimeoutId.get(
|
|
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
|
|
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(
|
|
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
|
|
10
|
-
if (!stores.has(
|
|
11
|
-
stores.set(
|
|
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(
|
|
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
|
|
22
|
+
const keyHash = hashKeyFn(key);
|
|
22
23
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
23
|
-
const { get, subscribe } = useMemo(() => getStore(key), [
|
|
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
|
-
}, [
|
|
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 {
|
|
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 =
|
|
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,
|
|
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(
|
|
74
|
+
clearTimeout(retryTimeoutId.get(keyHash));
|
|
74
75
|
}
|
|
75
76
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
76
|
-
preventReplaceResponse.set(
|
|
77
|
+
preventReplaceResponse.set(keyHash, false);
|
|
77
78
|
queryFn(key, stateBeforeCallQuery)
|
|
78
79
|
.then((response) => {
|
|
79
|
-
if (preventReplaceResponse.get(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
145
|
+
hasNextPage: hasValue(pageParam),
|
|
145
146
|
});
|
|
146
147
|
if (shouldRetry) {
|
|
147
|
-
retryTimeoutId.set(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
248
|
-
clearTimeout(retryNextPageTimeoutId.get(
|
|
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
|
|
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(
|
|
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
|
|
10
|
-
if (!stores.has(
|
|
11
|
-
stores.set(
|
|
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(
|
|
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
|
|
22
|
+
const keyHash = hashKeyFn(key);
|
|
22
23
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
23
|
-
const { get, subscribe } = useMemo(() => getStore(key), [
|
|
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
|
-
}, [
|
|
38
|
+
}, [keyHash]);
|
|
38
39
|
return state;
|
|
39
40
|
};
|
|
40
41
|
useStores.get = (key) => {
|
package/esm/utils/index.d.ts
CHANGED
package/esm/utils/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export const noop = () => { };
|
|
2
|
-
export const identityFn = (
|
|
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
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 =
|
|
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,
|
|
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(
|
|
77
|
+
clearTimeout(retryTimeoutId.get(keyHash));
|
|
77
78
|
}
|
|
78
79
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
79
|
-
preventReplaceResponse.set(
|
|
80
|
+
preventReplaceResponse.set(keyHash, false);
|
|
80
81
|
queryFn(key, stateBeforeCallQuery)
|
|
81
82
|
.then((response) => {
|
|
82
|
-
if (preventReplaceResponse.get(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
148
|
+
hasNextPage: (0, utils_1.hasValue)(pageParam),
|
|
148
149
|
});
|
|
149
150
|
if (shouldRetry) {
|
|
150
|
-
retryTimeoutId.set(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
251
|
-
clearTimeout(retryNextPageTimeoutId.get(
|
|
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
|
|
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(
|
|
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
|
|
13
|
-
if (!stores.has(
|
|
14
|
-
stores.set(
|
|
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(
|
|
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
|
|
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), [
|
|
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
|
-
}, [
|
|
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 =
|
|
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,
|
|
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(
|
|
77
|
+
clearTimeout(retryTimeoutId.get(keyHash));
|
|
77
78
|
}
|
|
78
79
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
79
|
-
preventReplaceResponse.set(
|
|
80
|
+
preventReplaceResponse.set(keyHash, false);
|
|
80
81
|
queryFn(key, stateBeforeCallQuery)
|
|
81
82
|
.then((response) => {
|
|
82
|
-
if (preventReplaceResponse.get(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
148
|
+
hasNextPage: (0, utils_1.hasValue)(pageParam),
|
|
148
149
|
});
|
|
149
150
|
if (shouldRetry) {
|
|
150
|
-
retryTimeoutId.set(
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
251
|
-
clearTimeout(retryNextPageTimeoutId.get(
|
|
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
|
|
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(
|
|
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
|
|
13
|
-
if (!stores.has(
|
|
14
|
-
stores.set(
|
|
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(
|
|
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
|
|
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), [
|
|
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
|
-
}, [
|
|
41
|
+
}, [keyHash]);
|
|
41
42
|
return state;
|
|
42
43
|
};
|
|
43
44
|
useStores.get = (key) => {
|
package/lib/utils/index.d.ts
CHANGED
package/lib/utils/index.js
CHANGED
|
@@ -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 = (
|
|
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;
|