floppy-disk 2.0.0-alpha.1 → 2.0.0-alpha.3
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 +33 -1
- package/esm/react/create-query.d.ts +6 -0
- package/esm/react/create-query.js +17 -10
- package/esm/react/create-stores.d.ts +1 -0
- package/esm/react/create-stores.js +1 -0
- package/lib/react/create-query.d.ts +6 -0
- package/lib/react/create-query.js +17 -10
- package/lib/react/create-stores.d.ts +1 -0
- package/lib/react/create-stores.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ import { createQuery, createMutation } from 'floppy-disk'; // 7.5 kB (gzipped: 2
|
|
|
32
32
|
- [Advanced Concept](#advanced-concept)
|
|
33
33
|
- [Stores](#stores)
|
|
34
34
|
- [Query \& Mutation](#query--mutation)
|
|
35
|
+
- [Query State \& Network Fetching State](#query-state--network-fetching-state)
|
|
35
36
|
- [Single Query](#single-query)
|
|
36
37
|
- [Single Query with Params](#single-query-with-params)
|
|
37
38
|
- [Paginated Query or Infinite Query](#paginated-query-or-infinite-query)
|
|
@@ -366,12 +367,43 @@ function Control({ catId }) {
|
|
|
366
367
|
|
|
367
368
|
> Example: [https://codesandbox.io/.../examples/react/stores](https://codesandbox.io/p/sandbox/github/afiiif/floppy-disk/tree/main/examples/react/stores)
|
|
368
369
|
|
|
370
|
+
<br><br>
|
|
371
|
+
|
|
372
|
+
<p align="center">
|
|
373
|
+
— ✨ 💾 ✨ —
|
|
374
|
+
</p>
|
|
375
|
+
<br>
|
|
376
|
+
|
|
369
377
|
## Query & Mutation
|
|
370
378
|
|
|
371
379
|
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.
|
|
372
380
|
|
|
373
381
|
It can dedupe multiple request, handle caching, auto-update stale data, handle retry on error, handle infinite query, and many more. With the flexibility given in `createStores`, you can extend its power according to your needs.
|
|
374
382
|
|
|
383
|
+
### Query State & Network Fetching State
|
|
384
|
+
|
|
385
|
+
There are 2 types of state: query (data) state & network fetching state.
|
|
386
|
+
|
|
387
|
+
`status`, `isLoading`, `isSuccess`, `isError` is a query data state.
|
|
388
|
+
It has no relation with network fetching state. ⚠️
|
|
389
|
+
Here is the flow of the query data state:
|
|
390
|
+
|
|
391
|
+
- Initial state when there is no data fetched.
|
|
392
|
+
`{ status: 'loading', isLoading: true, isSuccess: false, isError: false }`
|
|
393
|
+
- After data fetching:
|
|
394
|
+
- If success
|
|
395
|
+
`{ status: 'success', isLoading: false, isSuccess: true, isError: false }`
|
|
396
|
+
- If error
|
|
397
|
+
`{ status: 'error', isLoading: false, isSuccess: false, isError: true }`
|
|
398
|
+
- After data fetched successfully, you will **always** get this state:
|
|
399
|
+
`{ status: 'success', isLoading: false, isSuccess: true, isError: false }`
|
|
400
|
+
- If a refetch is fired and got error, the state would be:
|
|
401
|
+
`{ status: 'success', isLoading: false, isSuccess: true, isError: false, isRefetchError: true }`
|
|
402
|
+
The previouse success response will be kept.
|
|
403
|
+
|
|
404
|
+
For network fetching state, we use `isWaiting`.
|
|
405
|
+
The value will be `true` if the query is called and still waiting for the response.
|
|
406
|
+
|
|
375
407
|
### Single Query
|
|
376
408
|
|
|
377
409
|
```jsx
|
|
@@ -419,7 +451,7 @@ function Actions() {
|
|
|
419
451
|
<>
|
|
420
452
|
<button onClick={fetch}>Call query if the query data is stale</button>
|
|
421
453
|
<button onClick={forceFetch}>Call query</button>
|
|
422
|
-
<button onClick={markAsStale}>
|
|
454
|
+
<button onClick={markAsStale}>Mark as stale</button>
|
|
423
455
|
<button onClick={reset}>Reset query</button>
|
|
424
456
|
</>
|
|
425
457
|
);
|
|
@@ -153,7 +153,13 @@ export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData =
|
|
|
153
153
|
key?: TKey;
|
|
154
154
|
response: TResponse;
|
|
155
155
|
}) => void;
|
|
156
|
+
/**
|
|
157
|
+
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
158
|
+
*/
|
|
156
159
|
invalidate: () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
162
|
+
*/
|
|
157
163
|
invalidateSpecificKey: (key?: TKey | null) => void;
|
|
158
164
|
/**
|
|
159
165
|
* Optimistic update.
|
|
@@ -44,6 +44,7 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
44
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;
|
|
45
45
|
const retryTimeoutId = new Map();
|
|
46
46
|
const retryNextPageTimeoutId = new Map();
|
|
47
|
+
const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
|
|
47
48
|
const useQuery = createStores(({ key: _key, get, set }) => {
|
|
48
49
|
const key = _key;
|
|
49
50
|
const getRetryProps = (error, retryCount) => {
|
|
@@ -72,8 +73,11 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
72
73
|
clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
|
|
73
74
|
}
|
|
74
75
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
76
|
+
preventReplaceResponse.set(hashKeyFn(key), false);
|
|
75
77
|
queryFn(key, stateBeforeCallQuery)
|
|
76
78
|
.then((response) => {
|
|
79
|
+
if (preventReplaceResponse.get(hashKeyFn(key)))
|
|
80
|
+
return;
|
|
77
81
|
responseAllPages.push(response);
|
|
78
82
|
const newPageParam = getNextPageParam(response, responseAllPages.length);
|
|
79
83
|
newPageParams.push(newPageParam);
|
|
@@ -115,9 +119,11 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
115
119
|
isWaiting: false,
|
|
116
120
|
isRefetching: false,
|
|
117
121
|
isRefetchError: true,
|
|
118
|
-
data: responseAllPages.
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
data: responseAllPages.length
|
|
123
|
+
? responseAllPages.reduce((prev, response) => {
|
|
124
|
+
return select(response, { key, data: prev });
|
|
125
|
+
}, null)
|
|
126
|
+
: prevState.data,
|
|
121
127
|
error,
|
|
122
128
|
errorUpdatedAt,
|
|
123
129
|
isGoingToRetry: shouldRetry,
|
|
@@ -278,16 +284,16 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
278
284
|
});
|
|
279
285
|
};
|
|
280
286
|
useQuery.invalidate = () => {
|
|
281
|
-
useQuery.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
287
|
+
useQuery.getStores().forEach((store) => {
|
|
288
|
+
const { set, getSubscribers } = store;
|
|
289
|
+
set({ responseUpdatedAt: null });
|
|
290
|
+
if (getSubscribers().size > 0)
|
|
291
|
+
store.get().forceFetch();
|
|
286
292
|
});
|
|
287
293
|
};
|
|
288
294
|
useQuery.invalidateSpecificKey = (key) => {
|
|
289
|
-
useQuery.
|
|
290
|
-
|
|
295
|
+
const { set, getSubscribers } = useQuery.getStore(key);
|
|
296
|
+
set({ responseUpdatedAt: null });
|
|
291
297
|
if (getSubscribers().size > 0)
|
|
292
298
|
useQuery.get(key).forceFetch();
|
|
293
299
|
};
|
|
@@ -301,6 +307,7 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
301
307
|
response: optimisticResponse,
|
|
302
308
|
data: select(optimisticResponse, { key: key, data: null }),
|
|
303
309
|
});
|
|
310
|
+
preventReplaceResponse.set(hashKeyFn(key), true);
|
|
304
311
|
const revert = () => {
|
|
305
312
|
useQuery.set(key, {
|
|
306
313
|
isOptimisticData: false,
|
|
@@ -25,6 +25,7 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
|
|
|
25
25
|
subscribe: (key: Maybe<TKey>, fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
|
|
26
26
|
getSubscribers: (key: Maybe<TKey>) => Subscribers<T>;
|
|
27
27
|
getStore: (key?: Maybe<TKey>) => InitStoreReturn<T>;
|
|
28
|
+
getStores: () => Map<string, InitStoreReturn<T>>;
|
|
28
29
|
/**
|
|
29
30
|
* Set default values inside a component.
|
|
30
31
|
*
|
|
@@ -75,6 +75,7 @@ export const createStores = (initializer, options = {}) => {
|
|
|
75
75
|
return store.getSubscribers();
|
|
76
76
|
};
|
|
77
77
|
useStores.getStore = (key) => getStore(key);
|
|
78
|
+
useStores.getStores = () => stores;
|
|
78
79
|
useStores.setDefaultValues = (key, value) => {
|
|
79
80
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
80
81
|
useState(() => {
|
|
@@ -153,7 +153,13 @@ export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData =
|
|
|
153
153
|
key?: TKey;
|
|
154
154
|
response: TResponse;
|
|
155
155
|
}) => void;
|
|
156
|
+
/**
|
|
157
|
+
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
158
|
+
*/
|
|
156
159
|
invalidate: () => void;
|
|
160
|
+
/**
|
|
161
|
+
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
162
|
+
*/
|
|
157
163
|
invalidateSpecificKey: (key?: TKey | null) => void;
|
|
158
164
|
/**
|
|
159
165
|
* Optimistic update.
|
|
@@ -47,6 +47,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
47
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;
|
|
48
48
|
const retryTimeoutId = new Map();
|
|
49
49
|
const retryNextPageTimeoutId = new Map();
|
|
50
|
+
const preventReplaceResponse = new Map(); // Prevent optimistic data to be replaced
|
|
50
51
|
const useQuery = (0, create_stores_1.createStores)(({ key: _key, get, set }) => {
|
|
51
52
|
const key = _key;
|
|
52
53
|
const getRetryProps = (error, retryCount) => {
|
|
@@ -75,8 +76,11 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
75
76
|
clearTimeout(retryTimeoutId.get(hashKeyFn(key)));
|
|
76
77
|
}
|
|
77
78
|
const stateBeforeCallQuery = { ...get(), pageParam };
|
|
79
|
+
preventReplaceResponse.set(hashKeyFn(key), false);
|
|
78
80
|
queryFn(key, stateBeforeCallQuery)
|
|
79
81
|
.then((response) => {
|
|
82
|
+
if (preventReplaceResponse.get(hashKeyFn(key)))
|
|
83
|
+
return;
|
|
80
84
|
responseAllPages.push(response);
|
|
81
85
|
const newPageParam = getNextPageParam(response, responseAllPages.length);
|
|
82
86
|
newPageParams.push(newPageParam);
|
|
@@ -118,9 +122,11 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
118
122
|
isWaiting: false,
|
|
119
123
|
isRefetching: false,
|
|
120
124
|
isRefetchError: true,
|
|
121
|
-
data: responseAllPages.
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
data: responseAllPages.length
|
|
126
|
+
? responseAllPages.reduce((prev, response) => {
|
|
127
|
+
return select(response, { key, data: prev });
|
|
128
|
+
}, null)
|
|
129
|
+
: prevState.data,
|
|
124
130
|
error,
|
|
125
131
|
errorUpdatedAt,
|
|
126
132
|
isGoingToRetry: shouldRetry,
|
|
@@ -281,16 +287,16 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
281
287
|
});
|
|
282
288
|
};
|
|
283
289
|
useQuery.invalidate = () => {
|
|
284
|
-
useQuery.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
290
|
+
useQuery.getStores().forEach((store) => {
|
|
291
|
+
const { set, getSubscribers } = store;
|
|
292
|
+
set({ responseUpdatedAt: null });
|
|
293
|
+
if (getSubscribers().size > 0)
|
|
294
|
+
store.get().forceFetch();
|
|
289
295
|
});
|
|
290
296
|
};
|
|
291
297
|
useQuery.invalidateSpecificKey = (key) => {
|
|
292
|
-
useQuery.
|
|
293
|
-
|
|
298
|
+
const { set, getSubscribers } = useQuery.getStore(key);
|
|
299
|
+
set({ responseUpdatedAt: null });
|
|
294
300
|
if (getSubscribers().size > 0)
|
|
295
301
|
useQuery.get(key).forceFetch();
|
|
296
302
|
};
|
|
@@ -304,6 +310,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
304
310
|
response: optimisticResponse,
|
|
305
311
|
data: select(optimisticResponse, { key: key, data: null }),
|
|
306
312
|
});
|
|
313
|
+
preventReplaceResponse.set(hashKeyFn(key), true);
|
|
307
314
|
const revert = () => {
|
|
308
315
|
useQuery.set(key, {
|
|
309
316
|
isOptimisticData: false,
|
|
@@ -25,6 +25,7 @@ export type UseStores<TKey extends StoreKey = StoreKey, T extends StoreData = St
|
|
|
25
25
|
subscribe: (key: Maybe<TKey>, fn: (state: T) => void, selectDeps?: SelectDeps<T>) => () => void;
|
|
26
26
|
getSubscribers: (key: Maybe<TKey>) => Subscribers<T>;
|
|
27
27
|
getStore: (key?: Maybe<TKey>) => InitStoreReturn<T>;
|
|
28
|
+
getStores: () => Map<string, InitStoreReturn<T>>;
|
|
28
29
|
/**
|
|
29
30
|
* Set default values inside a component.
|
|
30
31
|
*
|
|
@@ -78,6 +78,7 @@ const createStores = (initializer, options = {}) => {
|
|
|
78
78
|
return store.getSubscribers();
|
|
79
79
|
};
|
|
80
80
|
useStores.getStore = (key) => getStore(key);
|
|
81
|
+
useStores.getStores = () => stores;
|
|
81
82
|
useStores.setDefaultValues = (key, value) => {
|
|
82
83
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
83
84
|
(0, react_1.useState)(() => {
|