floppy-disk 2.0.0-alpha.2 → 2.0.0-alpha.4
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 +49 -6
- package/esm/react/create-query.d.ts +8 -1
- package/esm/react/create-query.js +19 -4
- package/lib/react/create-query.d.ts +8 -1
- package/lib/react/create-query.js +19 -4
- 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
|
);
|
|
@@ -631,7 +663,17 @@ const useCartStore = createStore(({ set, get }) => ({
|
|
|
631
663
|
}));
|
|
632
664
|
```
|
|
633
665
|
|
|
634
|
-
|
|
666
|
+
Don't use conditional reactivity selector.
|
|
667
|
+
|
|
668
|
+
```jsx
|
|
669
|
+
function Cat({ isSomething }) {
|
|
670
|
+
const { age } = useCatStore(isSomething ? (state) => [state.age] : null); // ❌
|
|
671
|
+
const { age } = useCatStore((state) => (isSomething ? [state.age] : [state.isSleeping])); // ❌
|
|
672
|
+
return <div>Cat's age: {age}</div>;
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
No need to memoize the reactivity selector.
|
|
635
677
|
|
|
636
678
|
```jsx
|
|
637
679
|
function Cat() {
|
|
@@ -641,11 +683,12 @@ function Cat() {
|
|
|
641
683
|
}
|
|
642
684
|
```
|
|
643
685
|
|
|
644
|
-
|
|
686
|
+
No need to memoize the store key / query key.
|
|
645
687
|
|
|
646
688
|
```jsx
|
|
647
|
-
function
|
|
648
|
-
const
|
|
649
|
-
|
|
689
|
+
function PokemonsPage() {
|
|
690
|
+
const queryKey = useMemo(() => ({ generation: 'ii', sort: 'asc' }), []); // ❌
|
|
691
|
+
const { isLoading, data } = usePokemonsQuery(queryKey);
|
|
692
|
+
return <div>...</div>;
|
|
650
693
|
}
|
|
651
694
|
```
|
|
@@ -21,7 +21,6 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
|
|
|
21
21
|
* You can ignore this if your query is not paginated.
|
|
22
22
|
*/
|
|
23
23
|
fetchNextPage: () => void;
|
|
24
|
-
markAsStale: () => void;
|
|
25
24
|
/**
|
|
26
25
|
* Set query state (data, error, etc) to initial state.
|
|
27
26
|
*/
|
|
@@ -153,6 +152,14 @@ export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData =
|
|
|
153
152
|
key?: TKey;
|
|
154
153
|
response: TResponse;
|
|
155
154
|
}) => void;
|
|
155
|
+
/**
|
|
156
|
+
* Set query state (data, error, etc) to initial state.
|
|
157
|
+
*/
|
|
158
|
+
reset: () => void;
|
|
159
|
+
/**
|
|
160
|
+
* Set query state (data, error, etc) to initial state.
|
|
161
|
+
*/
|
|
162
|
+
resetSpecificKey: (key?: TKey | null) => void;
|
|
156
163
|
/**
|
|
157
164
|
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
158
165
|
*/
|
|
@@ -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,
|
|
@@ -200,7 +206,6 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
200
206
|
fetch,
|
|
201
207
|
forceFetch,
|
|
202
208
|
fetchNextPage,
|
|
203
|
-
markAsStale: () => set({ responseUpdatedAt: null }),
|
|
204
209
|
reset: () => set(INITIAL_QUERY_STATE),
|
|
205
210
|
};
|
|
206
211
|
}, (() => {
|
|
@@ -277,6 +282,15 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
277
282
|
});
|
|
278
283
|
});
|
|
279
284
|
};
|
|
285
|
+
useQuery.reset = () => {
|
|
286
|
+
useQuery.getStores().forEach((store) => {
|
|
287
|
+
store.set(INITIAL_QUERY_STATE);
|
|
288
|
+
});
|
|
289
|
+
};
|
|
290
|
+
useQuery.resetSpecificKey = (key) => {
|
|
291
|
+
const store = useQuery.getStore(key);
|
|
292
|
+
store.set(INITIAL_QUERY_STATE);
|
|
293
|
+
};
|
|
280
294
|
useQuery.invalidate = () => {
|
|
281
295
|
useQuery.getStores().forEach((store) => {
|
|
282
296
|
const { set, getSubscribers } = store;
|
|
@@ -301,6 +315,7 @@ export const createQuery = (queryFn, options = {}) => {
|
|
|
301
315
|
response: optimisticResponse,
|
|
302
316
|
data: select(optimisticResponse, { key: key, data: null }),
|
|
303
317
|
});
|
|
318
|
+
preventReplaceResponse.set(hashKeyFn(key), true);
|
|
304
319
|
const revert = () => {
|
|
305
320
|
useQuery.set(key, {
|
|
306
321
|
isOptimisticData: false,
|
|
@@ -21,7 +21,6 @@ export type QueryState<TKey extends StoreKey = StoreKey, TResponse = any, TData
|
|
|
21
21
|
* You can ignore this if your query is not paginated.
|
|
22
22
|
*/
|
|
23
23
|
fetchNextPage: () => void;
|
|
24
|
-
markAsStale: () => void;
|
|
25
24
|
/**
|
|
26
25
|
* Set query state (data, error, etc) to initial state.
|
|
27
26
|
*/
|
|
@@ -153,6 +152,14 @@ export type UseQuery<TKey extends StoreKey = StoreKey, TResponse = any, TData =
|
|
|
153
152
|
key?: TKey;
|
|
154
153
|
response: TResponse;
|
|
155
154
|
}) => void;
|
|
155
|
+
/**
|
|
156
|
+
* Set query state (data, error, etc) to initial state.
|
|
157
|
+
*/
|
|
158
|
+
reset: () => void;
|
|
159
|
+
/**
|
|
160
|
+
* Set query state (data, error, etc) to initial state.
|
|
161
|
+
*/
|
|
162
|
+
resetSpecificKey: (key?: TKey | null) => void;
|
|
156
163
|
/**
|
|
157
164
|
* Invalidate query means marking a query as stale, and will refetch only if the query is active (has subscriber)
|
|
158
165
|
*/
|
|
@@ -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,
|
|
@@ -203,7 +209,6 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
203
209
|
fetch,
|
|
204
210
|
forceFetch,
|
|
205
211
|
fetchNextPage,
|
|
206
|
-
markAsStale: () => set({ responseUpdatedAt: null }),
|
|
207
212
|
reset: () => set(INITIAL_QUERY_STATE),
|
|
208
213
|
};
|
|
209
214
|
}, (() => {
|
|
@@ -280,6 +285,15 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
280
285
|
});
|
|
281
286
|
});
|
|
282
287
|
};
|
|
288
|
+
useQuery.reset = () => {
|
|
289
|
+
useQuery.getStores().forEach((store) => {
|
|
290
|
+
store.set(INITIAL_QUERY_STATE);
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
useQuery.resetSpecificKey = (key) => {
|
|
294
|
+
const store = useQuery.getStore(key);
|
|
295
|
+
store.set(INITIAL_QUERY_STATE);
|
|
296
|
+
};
|
|
283
297
|
useQuery.invalidate = () => {
|
|
284
298
|
useQuery.getStores().forEach((store) => {
|
|
285
299
|
const { set, getSubscribers } = store;
|
|
@@ -304,6 +318,7 @@ const createQuery = (queryFn, options = {}) => {
|
|
|
304
318
|
response: optimisticResponse,
|
|
305
319
|
data: select(optimisticResponse, { key: key, data: null }),
|
|
306
320
|
});
|
|
321
|
+
preventReplaceResponse.set(hashKeyFn(key), true);
|
|
307
322
|
const revert = () => {
|
|
308
323
|
useQuery.set(key, {
|
|
309
324
|
isOptimisticData: false,
|