floppy-disk 1.0.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/LICENSE +21 -0
- package/README.md +612 -0
- package/esm/index.d.ts +6 -0
- package/esm/index.js +6 -0
- package/esm/react/create-mutation.d.ts +21 -0
- package/esm/react/create-mutation.js +48 -0
- package/esm/react/create-query.d.ts +150 -0
- package/esm/react/create-query.js +251 -0
- package/esm/react/create-store.d.ts +22 -0
- package/esm/react/create-store.js +25 -0
- package/esm/react/create-stores.d.ts +34 -0
- package/esm/react/create-stores.js +83 -0
- package/esm/react/with-context.d.ts +5 -0
- package/esm/react/with-context.js +14 -0
- package/esm/utils/index.d.ts +2 -0
- package/esm/utils/index.js +2 -0
- package/esm/vanilla.d.ts +23 -0
- package/esm/vanilla.js +57 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +9 -0
- package/lib/react/create-mutation.d.ts +21 -0
- package/lib/react/create-mutation.js +52 -0
- package/lib/react/create-query.d.ts +150 -0
- package/lib/react/create-query.js +255 -0
- package/lib/react/create-store.d.ts +22 -0
- package/lib/react/create-store.js +29 -0
- package/lib/react/create-stores.d.ts +34 -0
- package/lib/react/create-stores.js +87 -0
- package/lib/react/with-context.d.ts +5 -0
- package/lib/react/with-context.js +19 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +7 -0
- package/lib/vanilla.d.ts +23 -0
- package/lib/vanilla.js +61 -0
- package/package.json +66 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Muhammad Afifudin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
# Floppy Disk 💾
|
|
2
|
+
|
|
3
|
+
A lightweight, simple, and powerful state management library.
|
|
4
|
+
|
|
5
|
+
This library was highly-inspired by [Zustand](https://www.npmjs.com/package/zustand) and [React-Query](https://tanstack.com/query). Both are awesome state manager, but I want to have something like that with **more power** and **less bundle size**.
|
|
6
|
+
|
|
7
|
+
**Bundle Size Comparison:**
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { create } from 'zustand'; // 3.3k (gzipped: 1.5k)
|
|
11
|
+
|
|
12
|
+
import { createStore } from 'floppy-disk'; // 1.1k (gzipped: 622) 🎉
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
QueryClient,
|
|
16
|
+
QueryClientProvider,
|
|
17
|
+
useQuery,
|
|
18
|
+
useInfiniteQuery,
|
|
19
|
+
useMutation,
|
|
20
|
+
} from '@tanstack/react-query'; // 41k (gzipped: 11k)
|
|
21
|
+
|
|
22
|
+
import { createQuery, createMutation } from 'floppy-disk'; // 6.1k (gzipped: 2.2k) 🎉
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Table of Contents
|
|
26
|
+
|
|
27
|
+
- [Store](#store)
|
|
28
|
+
- [Basic Concept](#basic-concept)
|
|
29
|
+
- [Advanced Concept](#advanced-concept)
|
|
30
|
+
- [Stores](#stores)
|
|
31
|
+
- [Query \& Mutation](#query--mutation)
|
|
32
|
+
- [Single Query](#single-query)
|
|
33
|
+
- [Single Query with Params](#single-query-with-params)
|
|
34
|
+
- [Paginated Query or Infinite Query](#paginated-query-or-infinite-query)
|
|
35
|
+
- [Mutation](#mutation)
|
|
36
|
+
- [Important Notes](#important-notes)
|
|
37
|
+
|
|
38
|
+
## Store
|
|
39
|
+
|
|
40
|
+
### Basic Concept
|
|
41
|
+
|
|
42
|
+
Create a store.
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
import { createStore } from 'floppy-disk';
|
|
46
|
+
|
|
47
|
+
const useCatStore = createStore(({ set }) => ({
|
|
48
|
+
age: 0,
|
|
49
|
+
isSleeping: false,
|
|
50
|
+
increaseAge: () => set((state) => ({ age: state.age + 1 })),
|
|
51
|
+
reset: () => set({ age: 0, isSleeping: false }),
|
|
52
|
+
}));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use the hook anywhere, no providers are needed.
|
|
56
|
+
|
|
57
|
+
```jsx
|
|
58
|
+
function Cat() {
|
|
59
|
+
const { age } = useCatStore((state) => [state.age]);
|
|
60
|
+
return <div>Cat's age: {age}</div>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Control() {
|
|
64
|
+
const { increaseAge } = useCatStore((state) => [state.increaseAge]);
|
|
65
|
+
return <button onClick={increaseAge}>Increase cat's age</button>;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Control the reactivity. The concept is same as useEffect dependency array.
|
|
70
|
+
|
|
71
|
+
```jsx
|
|
72
|
+
function Cat() {
|
|
73
|
+
const { age, isSleeping } = useCatStore();
|
|
74
|
+
// Will re-render every state change ^
|
|
75
|
+
return <div>...</div>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function Cat() {
|
|
79
|
+
const { age, isSleeping } = useCatStore((state) => [state.isSleeping]);
|
|
80
|
+
// Will only re-render when isSleeping is updated ^
|
|
81
|
+
// Update on age won't cause re-render this component
|
|
82
|
+
return <div>...</div>;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function Cat() {
|
|
86
|
+
const { age, isSleeping } = useCatStore((state) => [state.age, state.isSleeping]);
|
|
87
|
+
// Will re-render when age or isSleeping is updated ^
|
|
88
|
+
return <div>...</div>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function Cat() {
|
|
92
|
+
const { age, isSleeping } = useCatStore((state) => [state.age > 3]);
|
|
93
|
+
// Will only re-render when (age>3) is updated
|
|
94
|
+
return <div>...</div>;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Reading/writing state and reacting to changes outside of components.
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
const alertCatAge = () => {
|
|
102
|
+
alert(useCatStore.get().age);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const toggleIsSleeping = () => {
|
|
106
|
+
useCatStore.set((state) => ({ isSleeping: !state.isSleeping }));
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const unsub = useCatStore.subscribe(
|
|
110
|
+
// Action
|
|
111
|
+
(state) => {
|
|
112
|
+
console.log('The value of age is changed!', state.age);
|
|
113
|
+
},
|
|
114
|
+
// Reactivity dependency (just like useEffect dependency mentioned above)
|
|
115
|
+
(state) => [state.age],
|
|
116
|
+
// ^If not set, the action will be triggered on every state change
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Advanced Concept
|
|
121
|
+
|
|
122
|
+
Set the state **silently** (without broadcast the state change to **any subscribers**).
|
|
123
|
+
|
|
124
|
+
```jsx
|
|
125
|
+
const decreaseAgeSilently = () => {
|
|
126
|
+
useCatStore.set((state) => ({ age: state.age }), true);
|
|
127
|
+
// ^silent param
|
|
128
|
+
};
|
|
129
|
+
// 👇 Will not re-render
|
|
130
|
+
function Cat() {
|
|
131
|
+
const { age } = useCatStore((state) => [state.age]);
|
|
132
|
+
return <div>Cat's age: {age}</div>;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Store events & interception.
|
|
137
|
+
|
|
138
|
+
```js
|
|
139
|
+
const useCatStore = createStore(
|
|
140
|
+
({ set }) => ({
|
|
141
|
+
age: 0,
|
|
142
|
+
isSleeping: false,
|
|
143
|
+
increaseAge: () => set((state) => ({ age: state.age + 1 })),
|
|
144
|
+
reset: () => set({ age: 0, isSleeping: false }),
|
|
145
|
+
}),
|
|
146
|
+
{
|
|
147
|
+
onFirstSubscribe: (state) => {
|
|
148
|
+
console.log('onFirstSubscribe', state);
|
|
149
|
+
},
|
|
150
|
+
onSubscribe: (state) => {
|
|
151
|
+
console.log('onSubscribe', state);
|
|
152
|
+
},
|
|
153
|
+
onUnsubscribe: (state) => {
|
|
154
|
+
console.log('onUnsubscribe', state);
|
|
155
|
+
},
|
|
156
|
+
onLastUnsubscribe: (state) => {
|
|
157
|
+
console.log('onLastUnsubscribe', state);
|
|
158
|
+
},
|
|
159
|
+
intercept: (nextState, prevState) => {
|
|
160
|
+
if (nextState.age !== prevState.age) {
|
|
161
|
+
return { ...nextState, isSleeping: false };
|
|
162
|
+
}
|
|
163
|
+
return nextState;
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Let's go wild using IIFE.
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
const useCatStore = createStore(
|
|
173
|
+
({ set }) => ({
|
|
174
|
+
age: 0,
|
|
175
|
+
isSleeping: false,
|
|
176
|
+
increaseAge: () => set((state) => ({ age: state.age + 1 })),
|
|
177
|
+
reset: () => set({ age: 0, isSleeping: false }),
|
|
178
|
+
}),
|
|
179
|
+
(() => {
|
|
180
|
+
const validateCat = () => {
|
|
181
|
+
console.info('Window focus event triggered...');
|
|
182
|
+
const { age } = useCatStore.get();
|
|
183
|
+
if (age > 5) useCatStore.set({ age: 1 });
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
onFirstSubscribe: () => window.addEventListener('focus', validateCat),
|
|
187
|
+
onLastUnsubscribe: () => window.removeEventListener('focus', validateCat),
|
|
188
|
+
};
|
|
189
|
+
})(),
|
|
190
|
+
);
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Prevent re-render using `Watch`.
|
|
194
|
+
|
|
195
|
+
```jsx
|
|
196
|
+
function CatPage() {
|
|
197
|
+
const { age } = useCatStore((state) => [state.age]);
|
|
198
|
+
// If age changed, this component will re-render which will cause
|
|
199
|
+
// HeavyComponent1 & HeavyComponent2 to be re-rendered as well.
|
|
200
|
+
return (
|
|
201
|
+
<main>
|
|
202
|
+
<HeavyComponent1 />
|
|
203
|
+
<div>Cat's age: {age}</div>
|
|
204
|
+
<HeavyComponent2 />
|
|
205
|
+
</main>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Optimized
|
|
210
|
+
function CatPageOptimized() {
|
|
211
|
+
return (
|
|
212
|
+
<main>
|
|
213
|
+
<HeavyComponent1 />
|
|
214
|
+
<useCatStore.Watch
|
|
215
|
+
selectDeps={(state) => [state.age]}
|
|
216
|
+
render={({ age }) => {
|
|
217
|
+
return <div>Cat's age: {age}</div>;
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
<HeavyComponent2 />
|
|
221
|
+
</main>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Want a local state instead of global state?
|
|
227
|
+
Or, want to set the initial state inside component?
|
|
228
|
+
|
|
229
|
+
```jsx
|
|
230
|
+
const [CatStoreProvider, useCatStoreContext] = withContext(() =>
|
|
231
|
+
createStore(({ set }) => ({
|
|
232
|
+
age: 0,
|
|
233
|
+
isSleeping: false,
|
|
234
|
+
increaseAge: () => set((state) => ({ age: state.age + 1 })),
|
|
235
|
+
reset: () => set({ age: 0, isSleeping: false }),
|
|
236
|
+
})),
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
function Parent() {
|
|
240
|
+
return (
|
|
241
|
+
<>
|
|
242
|
+
<CatStoreProvider>
|
|
243
|
+
<CatAge />
|
|
244
|
+
<CatIsSleeping />
|
|
245
|
+
<WillNotReRenderAsCatStateChanged />
|
|
246
|
+
</CatStoreProvider>
|
|
247
|
+
|
|
248
|
+
<CatStoreProvider>
|
|
249
|
+
<CatAge />
|
|
250
|
+
<CatIsSleeping />
|
|
251
|
+
<WillNotReRenderAsCatStateChanged />
|
|
252
|
+
</CatStoreProvider>
|
|
253
|
+
|
|
254
|
+
<CatStoreProvider onInitialize={(store) => store.set({ age: 99 })}>
|
|
255
|
+
<CatAge />
|
|
256
|
+
<CatIsSleeping />
|
|
257
|
+
<WillNotReRenderAsCatStateChanged />
|
|
258
|
+
</CatStoreProvider>
|
|
259
|
+
</>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function CatAge() {
|
|
264
|
+
const { age } = useCatStoreContext()((state) => [state.age]);
|
|
265
|
+
return <div>Age: {age}</div>;
|
|
266
|
+
}
|
|
267
|
+
function CatIsSleeping() {
|
|
268
|
+
const useCatStore = useCatStoreContext();
|
|
269
|
+
const { isSleeping } = useCatStore((state) => [state.isSleeping]);
|
|
270
|
+
return (
|
|
271
|
+
<>
|
|
272
|
+
<div>Is Sleeping: {String(isSleeping)}</div>
|
|
273
|
+
<button onClick={useCatStore.get().increase}>Increase cat age</button>
|
|
274
|
+
</>
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Set default reactivity.
|
|
280
|
+
|
|
281
|
+
```jsx
|
|
282
|
+
const useCatStore = createStore(
|
|
283
|
+
({ set }) => ({
|
|
284
|
+
age: 0,
|
|
285
|
+
isSleeping: false,
|
|
286
|
+
increaseAge: () => set((state) => ({ age: state.age + 1 })),
|
|
287
|
+
reset: () => set({ age: 0, isSleeping: false }),
|
|
288
|
+
}),
|
|
289
|
+
{
|
|
290
|
+
defaultDeps: (state) => [state.age], // 👈
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
function Cat() {
|
|
295
|
+
const { age } = useCatStore();
|
|
296
|
+
// ^will only re-render when age changed
|
|
297
|
+
return <div>Cat's age: {age}</div>;
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Stores
|
|
302
|
+
|
|
303
|
+
The concept is same as [store](#store), but this can be used for multiple stores.
|
|
304
|
+
|
|
305
|
+
You need to specify the store key (an object) as identifier.
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
import { createStores } from 'floppy-disk';
|
|
309
|
+
|
|
310
|
+
const useCatStores = createStores(
|
|
311
|
+
({ set, get, key }) => ({
|
|
312
|
+
// ^store key
|
|
313
|
+
age: 0,
|
|
314
|
+
isSleeping: false,
|
|
315
|
+
increaseAge: () => set((state) => ({ age: state.age + 1 })),
|
|
316
|
+
reset: () => set({ age: 0, isSleeping: false }),
|
|
317
|
+
}),
|
|
318
|
+
{
|
|
319
|
+
onBeforeChangeKey: (nextKey, prevKey) => {
|
|
320
|
+
console.log('Store key changed', nextKey, prevKey);
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
function CatPage() {
|
|
326
|
+
const [catId, setCatId] = useState(1);
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<>
|
|
330
|
+
<div>Current cat id: {catId}</div>
|
|
331
|
+
<button onClick={() => setCatId((prev) => prev - 1)}>Prev cat</button>
|
|
332
|
+
<button onClick={() => setCatId((prev) => prev + 1)}>Next cat</button>
|
|
333
|
+
|
|
334
|
+
<Cat catId={catId} />
|
|
335
|
+
<Control catId={catId} />
|
|
336
|
+
</>
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function Cat({ catId }) {
|
|
341
|
+
const { age } = useCatStores({ catId }, (state) => [state.age]);
|
|
342
|
+
return <div>Cat's age: {age}</div>;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function Control({ catId }) {
|
|
346
|
+
const { increaseAge } = useCatStores({ catId }, (state) => [state.increaseAge]);
|
|
347
|
+
return <button onClick={increaseAge}>Increase cat's age</button>;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Query & Mutation
|
|
352
|
+
|
|
353
|
+
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.
|
|
354
|
+
|
|
355
|
+
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.
|
|
356
|
+
|
|
357
|
+
### Single Query
|
|
358
|
+
|
|
359
|
+
```jsx
|
|
360
|
+
const useGitHubQuery = createQuery(async () => {
|
|
361
|
+
const res = await fetch('https://api.github.com/repos/afiiif/floppy-disk');
|
|
362
|
+
if (res.ok) return res.json();
|
|
363
|
+
throw res;
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
function SingleQuery() {
|
|
367
|
+
const { isLoading, data } = useGitHubQuery();
|
|
368
|
+
|
|
369
|
+
if (isLoading) return <div>Loading...</div>;
|
|
370
|
+
|
|
371
|
+
return (
|
|
372
|
+
<div>
|
|
373
|
+
<h1>{data.name}</h1>
|
|
374
|
+
<p>{data.description}</p>
|
|
375
|
+
<strong>⭐️ {data.stargazers_count}</strong>
|
|
376
|
+
<strong>🍴 {data.forks_count}</strong>
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Custom reactivity:
|
|
383
|
+
|
|
384
|
+
```jsx
|
|
385
|
+
// This component doesn't care whether the query is success or error.
|
|
386
|
+
// It just listening to network fetching state. 👇
|
|
387
|
+
function SingleQueryLoader() {
|
|
388
|
+
const { isWaiting } = useGitHubQuery((state) => [state.isWaiting]);
|
|
389
|
+
return <div>Is network fetching? {String(isWaiting)}</div>;
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Actions:
|
|
394
|
+
|
|
395
|
+
```jsx
|
|
396
|
+
function Actions() {
|
|
397
|
+
const { fetch, forceFetch, markAsStale, reset } = useGitHubQuery(() => []);
|
|
398
|
+
return (
|
|
399
|
+
<>
|
|
400
|
+
<button onClick={fetch}>Call query if the query data is stale</button>
|
|
401
|
+
<button onClick={forceFetch}>Call query</button>
|
|
402
|
+
<button onClick={markAsStale}>Invalidate query</button>
|
|
403
|
+
<button onClick={reset}>Reset query</button>
|
|
404
|
+
</>
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Options:
|
|
410
|
+
|
|
411
|
+
```jsx
|
|
412
|
+
const useGitHubQuery = createQuery(
|
|
413
|
+
async () => {
|
|
414
|
+
const res = await fetch('https://api.github.com/repos/afiiif/floppy-disk');
|
|
415
|
+
if (res.ok) return res.json();
|
|
416
|
+
throw res;
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
fetchOnMount: false,
|
|
420
|
+
enabled: () => !!useUserQuery.get().data?.user,
|
|
421
|
+
select: (response) => response.name
|
|
422
|
+
staleTime: Infinity, // Never stale
|
|
423
|
+
retry: 0, // No retry
|
|
424
|
+
onSuccess: (response) => {},
|
|
425
|
+
onError: (error) => {},
|
|
426
|
+
onSettled: () => {},
|
|
427
|
+
},
|
|
428
|
+
);
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
Get data or do something outside component:
|
|
432
|
+
|
|
433
|
+
```jsx
|
|
434
|
+
const getData = () => {
|
|
435
|
+
console.log(useGitHubQuery.get().data);
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const resetQuery = () => {
|
|
439
|
+
useGitHubQuery.get().reset();
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
function Actions() {
|
|
443
|
+
return (
|
|
444
|
+
<>
|
|
445
|
+
<button onClick={getData}>Get Data</button>
|
|
446
|
+
<button onClick={resetQuery}>Reset query</button>
|
|
447
|
+
</>
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### Single Query with Params
|
|
453
|
+
|
|
454
|
+
```jsx
|
|
455
|
+
const usePokemonQuery = createQuery(async ({ pokemon }) => {
|
|
456
|
+
const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemon}`);
|
|
457
|
+
if (res.ok) return res.json();
|
|
458
|
+
throw res;
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
function PokemonPage() {
|
|
462
|
+
const [currentPokemon, setCurrentPokemon] = useState();
|
|
463
|
+
const { isLoading, data } = usePokemonQuery({ pokemon: currentPokemon });
|
|
464
|
+
|
|
465
|
+
if (isLoading) return <div>Loading...</div>;
|
|
466
|
+
|
|
467
|
+
return (
|
|
468
|
+
<div>
|
|
469
|
+
<h1>{data.name}</h1>
|
|
470
|
+
<div>Weight: {data.weight}</div>
|
|
471
|
+
</div>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Get data or do something outside component:
|
|
477
|
+
|
|
478
|
+
```jsx
|
|
479
|
+
const getDitto = () => {
|
|
480
|
+
console.log(usePokemonQuery.get({ pokemon: 'ditto' }).data);
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const resetDitto = () => {
|
|
484
|
+
usePokemonQuery.get({ pokemon: 'ditto' }).reset();
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
function Actions() {
|
|
488
|
+
return (
|
|
489
|
+
<>
|
|
490
|
+
<button onClick={getDitto}>Get Ditto Data</button>
|
|
491
|
+
<button onClick={resetDitto}>Reset Ditto</button>
|
|
492
|
+
</>
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### Paginated Query or Infinite Query
|
|
498
|
+
|
|
499
|
+
```jsx
|
|
500
|
+
const usePokemonsInfQuery = createQuery(
|
|
501
|
+
async (_, { pageParam = 0 }) => {
|
|
502
|
+
const res = await fetch(`https://pokeapi.co/api/v2/pokemon?limit=10&offset=${pageParam}`);
|
|
503
|
+
if (res.ok) return res.json();
|
|
504
|
+
throw res;
|
|
505
|
+
},
|
|
506
|
+
{
|
|
507
|
+
select: (response, { data }) => [...(data || []), ...response.results],
|
|
508
|
+
getNextPageParam: (lastPageResponse, i) => {
|
|
509
|
+
if (i > 5) return undefined; // Return undefined means you have reached the end of the pages
|
|
510
|
+
return i * 10;
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
function PokemonListPage() {
|
|
516
|
+
const { data, fetchNextPage, hasNextPage, isWaitingNextPage } = usePokemonsInfQuery();
|
|
517
|
+
|
|
518
|
+
return (
|
|
519
|
+
<div>
|
|
520
|
+
{data?.map((pokemon) => (
|
|
521
|
+
<div key={pokemon.name}>{pokemon.name}</div>
|
|
522
|
+
))}
|
|
523
|
+
{isWaitingNextPage ? (
|
|
524
|
+
<div>Loading more...</div>
|
|
525
|
+
) : (
|
|
526
|
+
hasNextPage && <button onClick={fetchNextPage}>Load more</button>
|
|
527
|
+
)}
|
|
528
|
+
</div>
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Note:**
|
|
534
|
+
|
|
535
|
+
- The default stale time is 3 seconds.
|
|
536
|
+
- The default reactivity of a query is `(state) => [state.data, state.error]`.
|
|
537
|
+
(For paginated query `(state) => [state.data, state.error, state.isWaitingNextPage, state.hasNextPage]`)
|
|
538
|
+
- You can change the `defaultDeps` on `createQuery` options.
|
|
539
|
+
|
|
540
|
+
### Mutation
|
|
541
|
+
|
|
542
|
+
```jsx
|
|
543
|
+
const useLoginMutation = createMutation(
|
|
544
|
+
async (variables) => {
|
|
545
|
+
const res = await axios.post('/auth/login', {
|
|
546
|
+
email: variables.email,
|
|
547
|
+
password: variables.password,
|
|
548
|
+
});
|
|
549
|
+
return res.data;
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
onSuccess: (response, variables) => {
|
|
553
|
+
console.log(`Logged in as ${variables.email}`);
|
|
554
|
+
console.log(`Access token: ${response.data.accessToken}`);
|
|
555
|
+
},
|
|
556
|
+
},
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
function Login() {
|
|
560
|
+
const { mutate, isWaiting } = useLoginMutation();
|
|
561
|
+
return (
|
|
562
|
+
<div>
|
|
563
|
+
<button
|
|
564
|
+
disabled={isWaiting}
|
|
565
|
+
onClick={() =>
|
|
566
|
+
mutate({ email: 'foo@bar.baz', password: 's3cREt' }).then(() => {
|
|
567
|
+
showToast('Login success');
|
|
568
|
+
})
|
|
569
|
+
}
|
|
570
|
+
>
|
|
571
|
+
Login
|
|
572
|
+
</button>
|
|
573
|
+
</div>
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
## Important Notes
|
|
579
|
+
|
|
580
|
+
Don't mutate.
|
|
581
|
+
|
|
582
|
+
```js
|
|
583
|
+
import { createStore } from 'floppy-disk';
|
|
584
|
+
|
|
585
|
+
const useCartStore = createStore(({ set, get }) => ({
|
|
586
|
+
products: [],
|
|
587
|
+
addProduct: (newProduct) => {
|
|
588
|
+
const currentProducts = get().products;
|
|
589
|
+
product.push(newProduct); // ❌ Don't mutate
|
|
590
|
+
set({ product });
|
|
591
|
+
},
|
|
592
|
+
}));
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
You don't need to memoize the reactivity selector.
|
|
596
|
+
|
|
597
|
+
```jsx
|
|
598
|
+
function Cat() {
|
|
599
|
+
const selectAge = useCallback((state) => [state.age], []); // ❌
|
|
600
|
+
const { age } = useCatStore(selectAge);
|
|
601
|
+
return <div>Cat's age: {age}</div>;
|
|
602
|
+
}
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
Don't use conditional reactivity selector.
|
|
606
|
+
|
|
607
|
+
```jsx
|
|
608
|
+
function Cat({ isSomething }) {
|
|
609
|
+
const { age } = useCatStore(isSomething ? (state) => [state.age] : null); // ❌
|
|
610
|
+
return <div>Cat's age: {age}</div>;
|
|
611
|
+
}
|
|
612
|
+
```
|
package/esm/index.d.ts
ADDED
package/esm/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { InitStoreOptions } from '../vanilla';
|
|
2
|
+
export declare type MutationState<TVar, TResponse = any, TError = unknown> = {
|
|
3
|
+
/**
|
|
4
|
+
* Network fetching status.
|
|
5
|
+
*/
|
|
6
|
+
isWaiting: boolean;
|
|
7
|
+
isSuccess: boolean;
|
|
8
|
+
isError: boolean;
|
|
9
|
+
response: TResponse | null;
|
|
10
|
+
responseUpdatedAt: number | null;
|
|
11
|
+
error: TError | null;
|
|
12
|
+
errorUpdatedAt: number | null;
|
|
13
|
+
mutate: (variables?: TVar) => Promise<TResponse>;
|
|
14
|
+
};
|
|
15
|
+
export declare type CreateMutationOptions<TVar, TResponse = any, TError = unknown> = InitStoreOptions<MutationState<TVar, TResponse, TError>> & {
|
|
16
|
+
onMutate?: (variables: TVar | undefined, inputState: MutationState<TVar, TResponse, TError>) => void;
|
|
17
|
+
onSuccess?: (response: TResponse, variables: TVar | undefined, inputState: MutationState<TVar, TResponse, TError>) => void;
|
|
18
|
+
onError?: (error: TError, variables: TVar | undefined, inputState: MutationState<TVar, TResponse, TError>) => void;
|
|
19
|
+
onSettled?: (variables: TVar | undefined, inputState: MutationState<TVar, TResponse, TError>) => void;
|
|
20
|
+
};
|
|
21
|
+
export declare const createMutation: <TVar, TResponse = any, TError = unknown>(mutationFn: (variables: TVar | undefined, state: MutationState<TVar, TResponse, TError>) => Promise<TResponse>, options?: CreateMutationOptions<TVar, TResponse, TError>) => import("./create-store").UseStore<MutationState<TVar, TResponse, TError>>;
|