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 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.1 kB (gzipped: 622 B) 🎉
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'; // 6.4 kB (gzipped: 2.3 kB) 🎉
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: **279.63 kB** ~ gzipped 89.35 kB
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 reactivity of a query is `(state) => [state.data, state.error]`.
556
- (For paginated query `(state) => [state.data, state.error, state.isWaitingNextPage, state.hasNextPage]`)
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, isGoingToRetry, pageParams } = get();
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
- const retryCount = typeof retry === 'function' ? retry(error, key) : retry;
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, isGoingToRetry: true });
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
- resetRetryCount(state.key);
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, isGoingToRetry, pageParams } = get();
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
- const retryCount = typeof retry === 'function' ? retry(error, key) : retry;
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, isGoingToRetry: true });
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
- resetRetryCount(state.key);
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "floppy-disk",
3
- "version": "1.4.0-beta.1",
3
+ "version": "1.4.0",
4
4
  "description": "FloppyDisk - lightweight, simple, and powerful state management library",
5
5
  "keywords": [
6
6
  "state",