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 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}>Invalidate query</button>
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
- You don't need to memoize the reactivity selector.
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
- Don't use conditional reactivity selector.
686
+ No need to memoize the store key / query key.
645
687
 
646
688
  ```jsx
647
- function Cat({ isSomething }) {
648
- const { age } = useCatStore(isSomething ? (state) => [state.age] : null); // ❌
649
- return <div>Cat's age: {age}</div>;
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.reduce((prev, response) => {
119
- return select(response, { key, data: prev });
120
- }, null),
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.reduce((prev, response) => {
122
- return select(response, { key, data: prev });
123
- }, null),
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "floppy-disk",
3
- "version": "2.0.0-alpha.2",
3
+ "version": "2.0.0-alpha.4",
4
4
  "description": "FloppyDisk - lightweight, simple, and powerful state management library",
5
5
  "keywords": [
6
6
  "state",