floppy-disk 1.4.0-beta.1 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -5
- package/esm/react/create-query.d.ts +2 -0
- package/esm/react/create-query.js +30 -11
- package/lib/react/create-query.d.ts +2 -0
- package/lib/react/create-query.js +30 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ This library was highly-inspired by [Zustand](https://www.npmjs.com/package/zust
|
|
|
9
9
|
```js
|
|
10
10
|
import { create } from 'zustand'; // 3.3 kB (gzipped: 1.5 kB)
|
|
11
11
|
|
|
12
|
-
import { createStore } from 'floppy-disk'; // 1.
|
|
12
|
+
import { createStore } from 'floppy-disk'; // 1.3 kB (gzipped: 702 B) 🎉
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
QueryClient,
|
|
@@ -19,13 +19,13 @@ import {
|
|
|
19
19
|
useMutation,
|
|
20
20
|
} from '@tanstack/react-query'; // 41 kB (gzipped: 11 kB)
|
|
21
21
|
|
|
22
|
-
import { createQuery, createMutation } from 'floppy-disk'; //
|
|
22
|
+
import { createQuery, createMutation } from 'floppy-disk'; // 7.5 kB (gzipped: 2.6 kB) 🎉
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
- Using Zustand & React-Query: https://demo-zustand-react-query.vercel.app/
|
|
26
26
|
👉 Total: **309.22 kB** ~ gzipped 97.66 kB
|
|
27
27
|
- Using Floppy Disk: https://demo-floppy-disk.vercel.app/
|
|
28
|
-
👉 Total: **
|
|
28
|
+
👉 Total: **282.86 kB** ~ gzipped 90.46 kB
|
|
29
29
|
|
|
30
30
|
## Table of Contents
|
|
31
31
|
|
|
@@ -338,6 +338,7 @@ const useCatStores = createStores(
|
|
|
338
338
|
onBeforeChangeKey: (nextKey, prevKey) => {
|
|
339
339
|
console.log('Store key changed', nextKey, prevKey);
|
|
340
340
|
},
|
|
341
|
+
// ... same as createStore
|
|
341
342
|
},
|
|
342
343
|
);
|
|
343
344
|
|
|
@@ -367,6 +368,10 @@ function Control({ catId }) {
|
|
|
367
368
|
}
|
|
368
369
|
```
|
|
369
370
|
|
|
371
|
+
> **Examples:**
|
|
372
|
+
>
|
|
373
|
+
> - [https://codesandbox.io/.../examples/react/stores](https://codesandbox.io/p/sandbox/github/afiiif/floppy-disk/tree/main/examples/react/stores)
|
|
374
|
+
|
|
370
375
|
## Query & Mutation
|
|
371
376
|
|
|
372
377
|
With the power of `createStores` function and a bit creativity, we can easily create a hook just like `useQuery` and `useInfiniteQuery` in [React-Query](https://tanstack.com/query) using `createQuery` function.
|
|
@@ -398,6 +403,10 @@ function SingleQuery() {
|
|
|
398
403
|
}
|
|
399
404
|
```
|
|
400
405
|
|
|
406
|
+
> **Examples:**
|
|
407
|
+
>
|
|
408
|
+
> - [https://codesandbox.io/.../examples/react/query](https://codesandbox.io/p/sandbox/github/afiiif/floppy-disk/tree/main/examples/react/query)
|
|
409
|
+
|
|
401
410
|
Custom reactivity:
|
|
402
411
|
|
|
403
412
|
```jsx
|
|
@@ -552,8 +561,9 @@ function PokemonListPage() {
|
|
|
552
561
|
**Note:**
|
|
553
562
|
|
|
554
563
|
- The default stale time is 3 seconds.
|
|
555
|
-
- The default
|
|
556
|
-
|
|
564
|
+
- The default error retry attempt is 1 time, and retry delay is 3 seconds.
|
|
565
|
+
- The default reactivity of a query is `(s) => [s.data, s.error]`.
|
|
566
|
+
(For paginated: `(s) => [s.data, s.error, s.isWaitingNextPage, s.hasNextPage]`)
|
|
557
567
|
- You can change the `defaultDeps` on `createQuery` options.
|
|
558
568
|
|
|
559
569
|
### Mutation
|
|
@@ -599,6 +609,17 @@ function Login() {
|
|
|
599
609
|
}
|
|
600
610
|
```
|
|
601
611
|
|
|
612
|
+
> **Examples:**
|
|
613
|
+
>
|
|
614
|
+
> - [https://codesandbox.io/.../examples/react/mutation](https://codesandbox.io/p/sandbox/github/afiiif/floppy-disk/tree/main/examples/react/mutation)
|
|
615
|
+
|
|
616
|
+
<br><br>
|
|
617
|
+
|
|
618
|
+
<p align="center">
|
|
619
|
+
— ✨ 💾 ✨ —
|
|
620
|
+
</p>
|
|
621
|
+
<br>
|
|
622
|
+
|
|
602
623
|
## Important Notes
|
|
603
624
|
|
|
604
625
|
Don't mutate.
|
|
@@ -94,6 +94,8 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
|
|
|
94
94
|
pageParam: any;
|
|
95
95
|
pageParams: any[];
|
|
96
96
|
hasNextPage: boolean;
|
|
97
|
+
retryNextPageCount: number;
|
|
98
|
+
isGoingToRetryNextPage: boolean;
|
|
97
99
|
};
|
|
98
100
|
export type CreateQueryOptions<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = CreateStoresOptions<TKey, QueryState<TKey, TResponse, TData, TError>> & {
|
|
99
101
|
select?: (response: TResponse, state: Pick<QueryState<TKey, TResponse, TData, TError>, 'data' | 'key'>) => TData;
|
|
@@ -29,6 +29,8 @@ const INITIAL_QUERY_STATE = {
|
|
|
29
29
|
pageParam: undefined,
|
|
30
30
|
pageParams: [undefined],
|
|
31
31
|
hasNextPage: false,
|
|
32
|
+
retryNextPageCount: 0,
|
|
33
|
+
isGoingToRetryNextPage: false,
|
|
32
34
|
};
|
|
33
35
|
const useQueryDefaultDeps = (state) => [
|
|
34
36
|
state.data,
|
|
@@ -39,13 +41,20 @@ const useQueryDefaultDeps = (state) => [
|
|
|
39
41
|
export const createQuery = (queryFn, options = {}) => {
|
|
40
42
|
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;
|
|
41
43
|
const retryTimeoutId = new Map();
|
|
44
|
+
const retryNextPageTimeoutId = new Map();
|
|
42
45
|
const useQuery = createStores(({ key: _key, get, set }) => {
|
|
43
46
|
const key = _key;
|
|
47
|
+
const getRetryProps = (error, retryCount) => {
|
|
48
|
+
const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
|
|
49
|
+
const shouldRetry = retryCount < maxRetryCount;
|
|
50
|
+
const delay = (typeof retryDelay === 'function' ? retryDelay(error, key) : retryDelay) || 0;
|
|
51
|
+
return { shouldRetry, delay };
|
|
52
|
+
};
|
|
44
53
|
const forceFetch = () => {
|
|
45
54
|
const responseAllPages = [];
|
|
46
55
|
const newPageParams = [undefined];
|
|
47
56
|
let pageParam = undefined;
|
|
48
|
-
const { isWaiting, isLoading,
|
|
57
|
+
const { isWaiting, isLoading, pageParams } = get();
|
|
49
58
|
if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
|
|
50
59
|
return;
|
|
51
60
|
if (isLoading)
|
|
@@ -53,11 +62,12 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
53
62
|
else
|
|
54
63
|
set({ isWaiting: true, isRefetching: true });
|
|
55
64
|
const callQuery = () => {
|
|
56
|
-
if (isGoingToRetry) {
|
|
65
|
+
if (get().isGoingToRetry) {
|
|
57
66
|
if (isLoading)
|
|
58
67
|
set({ isGoingToRetry: false, isWaiting: true });
|
|
59
68
|
else
|
|
60
69
|
set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
|
|
70
|
+
clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
|
|
61
71
|
}
|
|
62
72
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
63
73
|
queryFn(key, stateBeforeCallQuery)
|
|
@@ -96,6 +106,7 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
96
106
|
.catch((error) => {
|
|
97
107
|
const prevState = get();
|
|
98
108
|
const errorUpdatedAt = Date.now();
|
|
109
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryCount);
|
|
99
110
|
set(prevState.isSuccess
|
|
100
111
|
? {
|
|
101
112
|
isWaiting: false,
|
|
@@ -106,6 +117,7 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
106
117
|
}, null),
|
|
107
118
|
error,
|
|
108
119
|
errorUpdatedAt,
|
|
120
|
+
isGoingToRetry: shouldRetry,
|
|
109
121
|
pageParam,
|
|
110
122
|
hasNextPage: pageParam !== undefined,
|
|
111
123
|
}
|
|
@@ -114,14 +126,13 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
114
126
|
isError: true,
|
|
115
127
|
error,
|
|
116
128
|
errorUpdatedAt,
|
|
129
|
+
isGoingToRetry: shouldRetry,
|
|
117
130
|
pageParam,
|
|
118
131
|
hasNextPage: pageParam !== undefined,
|
|
119
132
|
});
|
|
120
|
-
|
|
121
|
-
if (typeof retryCount === 'number' && prevState.retryCount < retryCount) {
|
|
122
|
-
const delay = typeof retryDelay === 'function' ? retryDelay(error, key) : retryDelay;
|
|
133
|
+
if (shouldRetry) {
|
|
123
134
|
retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
124
|
-
set({ retryCount: prevState.retryCount + 1
|
|
135
|
+
set({ retryCount: prevState.retryCount + 1 });
|
|
125
136
|
callQuery();
|
|
126
137
|
}, delay));
|
|
127
138
|
}
|
|
@@ -147,7 +158,8 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
147
158
|
return forceFetch();
|
|
148
159
|
if (isWaitingNextPage || !hasNextPage)
|
|
149
160
|
return;
|
|
150
|
-
set({ isWaitingNextPage: true });
|
|
161
|
+
set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
|
|
162
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
|
|
151
163
|
queryFn(key, { ...state, pageParam })
|
|
152
164
|
.then((response) => {
|
|
153
165
|
const newPageParam = getNextPageParam(response, pageParams.length);
|
|
@@ -162,12 +174,21 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
162
174
|
});
|
|
163
175
|
})
|
|
164
176
|
.catch((error) => {
|
|
177
|
+
const prevState = get();
|
|
178
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryNextPageCount);
|
|
165
179
|
set({
|
|
166
180
|
isWaitingNextPage: false,
|
|
167
181
|
isError: true,
|
|
168
182
|
error,
|
|
169
183
|
errorUpdatedAt: Date.now(),
|
|
184
|
+
isGoingToRetryNextPage: shouldRetry,
|
|
170
185
|
});
|
|
186
|
+
if (shouldRetry) {
|
|
187
|
+
retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
188
|
+
set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
|
|
189
|
+
fetchNextPage();
|
|
190
|
+
}, delay));
|
|
191
|
+
}
|
|
171
192
|
});
|
|
172
193
|
};
|
|
173
194
|
const setResponse = (responseSetter) => {
|
|
@@ -218,9 +239,6 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
218
239
|
},
|
|
219
240
|
};
|
|
220
241
|
}, (() => {
|
|
221
|
-
const resetRetryCount = (key) => {
|
|
222
|
-
useQuery.set(key, { retryCount: 0 }, true);
|
|
223
|
-
};
|
|
224
242
|
const fetchWindowFocusHandler = () => {
|
|
225
243
|
useQuery.getAllWithSubscriber().forEach((state) => {
|
|
226
244
|
getDecision(fetchOnWindowFocus, state.key, {
|
|
@@ -250,8 +268,9 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
250
268
|
if (typeof window !== 'undefined' && fetchOnWindowFocus) {
|
|
251
269
|
window.removeEventListener('focus', fetchWindowFocusHandler);
|
|
252
270
|
}
|
|
253
|
-
|
|
271
|
+
useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
|
|
254
272
|
clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
|
|
273
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
|
|
255
274
|
onLastUnsubscribe(state);
|
|
256
275
|
},
|
|
257
276
|
onBeforeChangeKey: (nextKey, prevKey) => {
|
|
@@ -94,6 +94,8 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
|
|
|
94
94
|
pageParam: any;
|
|
95
95
|
pageParams: any[];
|
|
96
96
|
hasNextPage: boolean;
|
|
97
|
+
retryNextPageCount: number;
|
|
98
|
+
isGoingToRetryNextPage: boolean;
|
|
97
99
|
};
|
|
98
100
|
export type CreateQueryOptions<TKey extends StoreKey = StoreKey, TResponse = any, TData = TResponse, TError = unknown> = CreateStoresOptions<TKey, QueryState<TKey, TResponse, TData, TError>> & {
|
|
99
101
|
select?: (response: TResponse, state: Pick<QueryState<TKey, TResponse, TData, TError>, 'data' | 'key'>) => TData;
|
|
@@ -32,6 +32,8 @@ const INITIAL_QUERY_STATE = {
|
|
|
32
32
|
pageParam: undefined,
|
|
33
33
|
pageParams: [undefined],
|
|
34
34
|
hasNextPage: false,
|
|
35
|
+
retryNextPageCount: 0,
|
|
36
|
+
isGoingToRetryNextPage: false,
|
|
35
37
|
};
|
|
36
38
|
const useQueryDefaultDeps = (state) => [
|
|
37
39
|
state.data,
|
|
@@ -42,13 +44,20 @@ const useQueryDefaultDeps = (state) => [
|
|
|
42
44
|
const createQuery = (queryFn, options = {}) => {
|
|
43
45
|
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;
|
|
44
46
|
const retryTimeoutId = new Map();
|
|
47
|
+
const retryNextPageTimeoutId = new Map();
|
|
45
48
|
const useQuery = (0, create_stores_1.createStores)(({ key: _key, get, set }) => {
|
|
46
49
|
const key = _key;
|
|
50
|
+
const getRetryProps = (error, retryCount) => {
|
|
51
|
+
const maxRetryCount = (typeof retry === 'function' ? retry(error, key) : retry) || 0;
|
|
52
|
+
const shouldRetry = retryCount < maxRetryCount;
|
|
53
|
+
const delay = (typeof retryDelay === 'function' ? retryDelay(error, key) : retryDelay) || 0;
|
|
54
|
+
return { shouldRetry, delay };
|
|
55
|
+
};
|
|
47
56
|
const forceFetch = () => {
|
|
48
57
|
const responseAllPages = [];
|
|
49
58
|
const newPageParams = [undefined];
|
|
50
59
|
let pageParam = undefined;
|
|
51
|
-
const { isWaiting, isLoading,
|
|
60
|
+
const { isWaiting, isLoading, pageParams } = get();
|
|
52
61
|
if (isWaiting || enabled === false || (typeof enabled === 'function' && !enabled(key)))
|
|
53
62
|
return;
|
|
54
63
|
if (isLoading)
|
|
@@ -56,11 +65,12 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
56
65
|
else
|
|
57
66
|
set({ isWaiting: true, isRefetching: true });
|
|
58
67
|
const callQuery = () => {
|
|
59
|
-
if (isGoingToRetry) {
|
|
68
|
+
if (get().isGoingToRetry) {
|
|
60
69
|
if (isLoading)
|
|
61
70
|
set({ isGoingToRetry: false, isWaiting: true });
|
|
62
71
|
else
|
|
63
72
|
set({ isGoingToRetry: false, isWaiting: true, isRefetching: true });
|
|
73
|
+
clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
|
|
64
74
|
}
|
|
65
75
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
66
76
|
queryFn(key, stateBeforeCallQuery)
|
|
@@ -99,6 +109,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
99
109
|
.catch((error) => {
|
|
100
110
|
const prevState = get();
|
|
101
111
|
const errorUpdatedAt = Date.now();
|
|
112
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryCount);
|
|
102
113
|
set(prevState.isSuccess
|
|
103
114
|
? {
|
|
104
115
|
isWaiting: false,
|
|
@@ -109,6 +120,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
109
120
|
}, null),
|
|
110
121
|
error,
|
|
111
122
|
errorUpdatedAt,
|
|
123
|
+
isGoingToRetry: shouldRetry,
|
|
112
124
|
pageParam,
|
|
113
125
|
hasNextPage: pageParam !== undefined,
|
|
114
126
|
}
|
|
@@ -117,14 +129,13 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
117
129
|
isError: true,
|
|
118
130
|
error,
|
|
119
131
|
errorUpdatedAt,
|
|
132
|
+
isGoingToRetry: shouldRetry,
|
|
120
133
|
pageParam,
|
|
121
134
|
hasNextPage: pageParam !== undefined,
|
|
122
135
|
});
|
|
123
|
-
|
|
124
|
-
if (typeof retryCount === 'number' && prevState.retryCount < retryCount) {
|
|
125
|
-
const delay = typeof retryDelay === 'function' ? retryDelay(error, key) : retryDelay;
|
|
136
|
+
if (shouldRetry) {
|
|
126
137
|
retryTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
127
|
-
set({ retryCount: prevState.retryCount + 1
|
|
138
|
+
set({ retryCount: prevState.retryCount + 1 });
|
|
128
139
|
callQuery();
|
|
129
140
|
}, delay));
|
|
130
141
|
}
|
|
@@ -150,7 +161,8 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
150
161
|
return forceFetch();
|
|
151
162
|
if (isWaitingNextPage || !hasNextPage)
|
|
152
163
|
return;
|
|
153
|
-
set({ isWaitingNextPage: true });
|
|
164
|
+
set({ isWaitingNextPage: true, isGoingToRetryNextPage: false });
|
|
165
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(key)));
|
|
154
166
|
queryFn(key, { ...state, pageParam })
|
|
155
167
|
.then((response) => {
|
|
156
168
|
const newPageParam = getNextPageParam(response, pageParams.length);
|
|
@@ -165,12 +177,21 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
165
177
|
});
|
|
166
178
|
})
|
|
167
179
|
.catch((error) => {
|
|
180
|
+
const prevState = get();
|
|
181
|
+
const { shouldRetry, delay } = getRetryProps(error, prevState.retryNextPageCount);
|
|
168
182
|
set({
|
|
169
183
|
isWaitingNextPage: false,
|
|
170
184
|
isError: true,
|
|
171
185
|
error,
|
|
172
186
|
errorUpdatedAt: Date.now(),
|
|
187
|
+
isGoingToRetryNextPage: shouldRetry,
|
|
173
188
|
});
|
|
189
|
+
if (shouldRetry) {
|
|
190
|
+
retryNextPageTimeoutId.set(hashKeyFn(key), window.setTimeout(() => {
|
|
191
|
+
set({ retryNextPageCount: prevState.retryNextPageCount + 1 });
|
|
192
|
+
fetchNextPage();
|
|
193
|
+
}, delay));
|
|
194
|
+
}
|
|
174
195
|
});
|
|
175
196
|
};
|
|
176
197
|
const setResponse = (responseSetter) => {
|
|
@@ -221,9 +242,6 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
221
242
|
},
|
|
222
243
|
};
|
|
223
244
|
}, (() => {
|
|
224
|
-
const resetRetryCount = (key) => {
|
|
225
|
-
useQuery.set(key, { retryCount: 0 }, true);
|
|
226
|
-
};
|
|
227
245
|
const fetchWindowFocusHandler = () => {
|
|
228
246
|
useQuery.getAllWithSubscriber().forEach((state) => {
|
|
229
247
|
getDecision(fetchOnWindowFocus, state.key, {
|
|
@@ -253,8 +271,9 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
253
271
|
if (typeof window !== 'undefined' && fetchOnWindowFocus) {
|
|
254
272
|
window.removeEventListener('focus', fetchWindowFocusHandler);
|
|
255
273
|
}
|
|
256
|
-
|
|
274
|
+
useQuery.set(state.key, { retryCount: 0, retryNextPageCount: 0 }, true);
|
|
257
275
|
clearTimeout(retryTimeoutId.get(hashKeyFn(state.key)));
|
|
276
|
+
clearTimeout(retryNextPageTimeoutId.get(hashKeyFn(state.key)));
|
|
258
277
|
onLastUnsubscribe(state);
|
|
259
278
|
},
|
|
260
279
|
onBeforeChangeKey: (nextKey, prevKey) => {
|