redux-unfold-saga-toolkit 1.0.1
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 +460 -0
- package/dist/action/index.d.ts +140 -0
- package/dist/action/index.js +175 -0
- package/dist/core/index.d.ts +47 -0
- package/dist/core/index.js +86 -0
- package/dist/helper/index.d.ts +2 -0
- package/dist/helper/index.js +2 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/reducer/index.d.ts +62 -0
- package/dist/reducer/index.js +83 -0
- package/dist/types/index.d.ts +40 -0
- package/dist/types/index.js +1 -0
- package/package.json +73 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Hung Nguyen
|
|
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,460 @@
|
|
|
1
|
+
# redux-unfold-saga-toolkit
|
|
2
|
+
|
|
3
|
+
A more user-friendly, headache-free redux-saga middleware.
|
|
4
|
+
|
|
5
|
+
> This library is inspired by [redux-unfold-saga](https://github.com/manhhailua/redux-unfold-saga).
|
|
6
|
+
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
#### Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save redux-unfold-saga-toolkit
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
or
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn add redux-unfold-saga-toolkit
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
> This library is required [redux-saga](https://redux-saga.js.org/docs/introduction/GettingStarted) and [immer](https://immerjs.github.io/immer/installation)
|
|
22
|
+
|
|
23
|
+
#### Usage example
|
|
24
|
+
|
|
25
|
+
- action
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { createAction } from 'redux-unfold-saga-toolkit';
|
|
29
|
+
|
|
30
|
+
const fetchPosts = createAction('FETCH_POSTS');
|
|
31
|
+
|
|
32
|
+
dispatch(
|
|
33
|
+
fetchPosts(
|
|
34
|
+
{ category: 'HOT' },
|
|
35
|
+
{
|
|
36
|
+
onBegin: () => {
|
|
37
|
+
// Do something before the query
|
|
38
|
+
setLoading(true);
|
|
39
|
+
},
|
|
40
|
+
onFailure: (error: Error) => {
|
|
41
|
+
// Do something in case of caught error
|
|
42
|
+
},
|
|
43
|
+
onSuccess: (posts: IPost[]) => {
|
|
44
|
+
// Do something after the query succeeded
|
|
45
|
+
},
|
|
46
|
+
onFinish: () => {
|
|
47
|
+
// Do something after everything is done
|
|
48
|
+
setLoading(false);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
),
|
|
52
|
+
);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- saga
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { call, takeLatest } from 'redux-saga/effects';
|
|
59
|
+
import { unfoldSaga } from 'redux-unfold-saga-toolkit';
|
|
60
|
+
|
|
61
|
+
function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
|
|
62
|
+
yield unfoldSaga({
|
|
63
|
+
action: action,
|
|
64
|
+
handler: function* () {
|
|
65
|
+
const data = yield call(ApiPost.listPost);
|
|
66
|
+
return data;
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function* defaultSaga() {
|
|
72
|
+
yield takeLatest('QUERY_POSTS', takeQueryPosts);
|
|
73
|
+
// yield takeLatest(fetchPosts, takeQueryPosts);
|
|
74
|
+
// yield takeLatest(fetchPosts.type, takeQueryPosts);
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- reducer
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { createReducer, createStoreAction } from 'redux-unfold-saga-toolkit';
|
|
82
|
+
|
|
83
|
+
const fetchPosts = createStoreAction('FETCH_POSTS');
|
|
84
|
+
|
|
85
|
+
const initState = {
|
|
86
|
+
posts: [],
|
|
87
|
+
error: null,
|
|
88
|
+
loading: true,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const postReducer = createReducer(initState, (builder) => {
|
|
92
|
+
builder.addCase<void>(fetchPosts.begin, (state, action) => {
|
|
93
|
+
state.loading = true;
|
|
94
|
+
});
|
|
95
|
+
builder.addCase<void>(fetchPosts.finish, (state, action) => {
|
|
96
|
+
state.loading = false;
|
|
97
|
+
});
|
|
98
|
+
builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
|
|
99
|
+
state.posts = action.payload;
|
|
100
|
+
});
|
|
101
|
+
builder.addCase<Error>(fetchPosts.failure, (state, action) => {
|
|
102
|
+
state.error = action.payload;
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## API
|
|
108
|
+
|
|
109
|
+
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
|
110
|
+
|
|
111
|
+
### Table of Contents
|
|
112
|
+
|
|
113
|
+
- [createActionTypeOnBegin][1]
|
|
114
|
+
- [Parameters][2]
|
|
115
|
+
- [Examples][3]
|
|
116
|
+
- [createActionTypeOnFinish][4]
|
|
117
|
+
- [Parameters][5]
|
|
118
|
+
- [Examples][6]
|
|
119
|
+
- [createActionTypeOnSuccess][7]
|
|
120
|
+
- [Parameters][8]
|
|
121
|
+
- [Examples][9]
|
|
122
|
+
- [createActionTypeOnFailure][10]
|
|
123
|
+
- [Parameters][11]
|
|
124
|
+
- [Examples][12]
|
|
125
|
+
- [createAction][13]
|
|
126
|
+
- [Parameters][14]
|
|
127
|
+
- [Examples][15]
|
|
128
|
+
- [createStoreAction][16]
|
|
129
|
+
- [Parameters][17]
|
|
130
|
+
- [Examples][18]
|
|
131
|
+
- [createReducer][19]
|
|
132
|
+
- [Parameters][20]
|
|
133
|
+
- [Examples][21]
|
|
134
|
+
- [unfoldSaga][22]
|
|
135
|
+
- [Parameters][23]
|
|
136
|
+
- [Examples][24]
|
|
137
|
+
|
|
138
|
+
## createActionTypeOnBegin
|
|
139
|
+
|
|
140
|
+
Create onBegin action type
|
|
141
|
+
|
|
142
|
+
### Parameters
|
|
143
|
+
|
|
144
|
+
- `key`  
|
|
145
|
+
|
|
146
|
+
### Examples
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
import { createActionTypeOnBegin } from 'redux-unfold-saga-toolkit';
|
|
150
|
+
|
|
151
|
+
createActionTypeOnBegin('DO_SOMETHING'); // DO_SOMETHING_BEGAN
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Returns **[string][25]** `${key}_BEGAN`
|
|
155
|
+
|
|
156
|
+
## createActionTypeOnFinish
|
|
157
|
+
|
|
158
|
+
Create onFinish action type
|
|
159
|
+
|
|
160
|
+
### Parameters
|
|
161
|
+
|
|
162
|
+
- `key`  
|
|
163
|
+
|
|
164
|
+
### Examples
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
import { createActionTypeOnFinish } from 'redux-unfold-saga-toolkit';
|
|
168
|
+
|
|
169
|
+
createActionTypeOnFinish('DO_SOMETHING'); // DO_SOMETHING_FINISHED
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Returns **[string][25]** `${key}_FINISHED`
|
|
173
|
+
|
|
174
|
+
## createActionTypeOnSuccess
|
|
175
|
+
|
|
176
|
+
Create onSuccess action type
|
|
177
|
+
|
|
178
|
+
### Parameters
|
|
179
|
+
|
|
180
|
+
- `key`  
|
|
181
|
+
|
|
182
|
+
### Examples
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
import { createActionTypeOnSuccess } from 'redux-unfold-saga-toolkit';
|
|
186
|
+
|
|
187
|
+
createActionTypeOnSuccess('DO_SOMETHING'); // DO_SOMETHING_SUCCEEDED
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Returns **[string][25]** `${key}_SUCCEEDED`
|
|
191
|
+
|
|
192
|
+
## createActionTypeOnFailure
|
|
193
|
+
|
|
194
|
+
Create onFailure action type
|
|
195
|
+
|
|
196
|
+
### Parameters
|
|
197
|
+
|
|
198
|
+
- `key`  
|
|
199
|
+
|
|
200
|
+
### Examples
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
import { createActionTypeOnFailure } from 'redux-unfold-saga-toolkit';
|
|
204
|
+
|
|
205
|
+
createActionTypeOnFailure('DO_SOMETHING'); // DO_SOMETHING_FAILED
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Returns **[string][25]** `${key}_FAILED`
|
|
209
|
+
|
|
210
|
+
## createAction
|
|
211
|
+
|
|
212
|
+
Create an action for real life usage inside or even outside of a component, no dispatch to reducer
|
|
213
|
+
|
|
214
|
+
### Parameters
|
|
215
|
+
|
|
216
|
+
- `type`  
|
|
217
|
+
|
|
218
|
+
### Examples
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
import {createAction} from 'redux-unfold-saga-toolkit';
|
|
222
|
+
|
|
223
|
+
const fetchPosts = createAction<IPostPayload>('FETCH_POSTS');
|
|
224
|
+
|
|
225
|
+
dispatch(
|
|
226
|
+
fetchPosts(
|
|
227
|
+
{category: 'HOT'},
|
|
228
|
+
{
|
|
229
|
+
onBegin: () => {
|
|
230
|
+
// Do something before the query
|
|
231
|
+
setLoading(true);
|
|
232
|
+
},
|
|
233
|
+
onFailure: (error: Error) => {
|
|
234
|
+
// Do something in case of caught error
|
|
235
|
+
},
|
|
236
|
+
onSuccess: (posts: IPost[]) => {
|
|
237
|
+
// Do something after the query succeeded
|
|
238
|
+
},
|
|
239
|
+
onFinish: () => {
|
|
240
|
+
// Do something after everything is done
|
|
241
|
+
setLoading(false);
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
),
|
|
245
|
+
);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Returns **UnfoldSagaActionCreator** action
|
|
249
|
+
|
|
250
|
+
## createStoreAction
|
|
251
|
+
|
|
252
|
+
Create an action for real life usage inside or even outside of a component, dispatch to reducer with automatic create action type
|
|
253
|
+
|
|
254
|
+
### Parameters
|
|
255
|
+
|
|
256
|
+
- `type`  
|
|
257
|
+
|
|
258
|
+
### Examples
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
// Action
|
|
262
|
+
import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
|
|
263
|
+
|
|
264
|
+
const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
|
|
265
|
+
|
|
266
|
+
dispatch(
|
|
267
|
+
fetchPosts(
|
|
268
|
+
{category: 'HOT'},
|
|
269
|
+
{
|
|
270
|
+
onBegin: () => {
|
|
271
|
+
// Do something before the query
|
|
272
|
+
setLoading(true);
|
|
273
|
+
},
|
|
274
|
+
onFailure: (error:Error) => {
|
|
275
|
+
// Do something in case of caught error
|
|
276
|
+
},
|
|
277
|
+
onSuccess: (posts:IPost) => {
|
|
278
|
+
// Do something after the query succeeded
|
|
279
|
+
},
|
|
280
|
+
onFinish: () => {
|
|
281
|
+
// Do something after everything is done
|
|
282
|
+
setLoading(false);
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// Reducer
|
|
289
|
+
const initState = {
|
|
290
|
+
posts: [],
|
|
291
|
+
error: null,
|
|
292
|
+
loading: true,
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const postReducer = createReducer(initState, (builder) => {
|
|
296
|
+
builder.addCase<void>(fetchPosts.begin, (state, action) => {
|
|
297
|
+
state.loading = true;
|
|
298
|
+
});
|
|
299
|
+
builder.addCase<void>(fetchPosts.finish, (state, action) => {
|
|
300
|
+
state.loading = false;
|
|
301
|
+
});
|
|
302
|
+
builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
|
|
303
|
+
state.posts = action.payload;
|
|
304
|
+
});
|
|
305
|
+
builder.addCase<Error>(fetchPosts.failure, (state, action) => {
|
|
306
|
+
state.error = action.payload;
|
|
307
|
+
});
|
|
308
|
+
})
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
Returns **UnfoldSagaActionCreator** action
|
|
312
|
+
|
|
313
|
+
## createReducer
|
|
314
|
+
|
|
315
|
+
A utility function that allows defining a reducer as a mapping from action
|
|
316
|
+
type to _case reducer_ functions that handle these action types. The
|
|
317
|
+
reducer's initial state is passed as the first argument.
|
|
318
|
+
|
|
319
|
+
### Parameters
|
|
320
|
+
|
|
321
|
+
- `initialState`  
|
|
322
|
+
- `builderCallback`  
|
|
323
|
+
|
|
324
|
+
### Examples
|
|
325
|
+
|
|
326
|
+
```javascript
|
|
327
|
+
// Action
|
|
328
|
+
|
|
329
|
+
import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
|
|
330
|
+
|
|
331
|
+
const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
|
|
332
|
+
|
|
333
|
+
dispatch(
|
|
334
|
+
fetchPosts(
|
|
335
|
+
{category: 'HOT'},
|
|
336
|
+
{
|
|
337
|
+
onBegin: () => {
|
|
338
|
+
// Do something before the query
|
|
339
|
+
setLoading(true);
|
|
340
|
+
},
|
|
341
|
+
onFailure: (error:Error) => {
|
|
342
|
+
// Do something in case of caught error
|
|
343
|
+
},
|
|
344
|
+
onSuccess: (posts:IPost) => {
|
|
345
|
+
// Do something after the query succeeded
|
|
346
|
+
},
|
|
347
|
+
onFinish: () => {
|
|
348
|
+
// Do something after everything is done
|
|
349
|
+
setLoading(false);
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// Reducer
|
|
356
|
+
const initState = {
|
|
357
|
+
posts: [],
|
|
358
|
+
error: null,
|
|
359
|
+
loading: true,
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
const postReducer = createReducer(initState, (builder) => {
|
|
363
|
+
builder.addCase<void>(fetchPosts.begin, (state, action) => {
|
|
364
|
+
state.loading = true;
|
|
365
|
+
});
|
|
366
|
+
builder.addCase<void>(fetchPosts.finish, (state, action) => {
|
|
367
|
+
state.loading = false;
|
|
368
|
+
});
|
|
369
|
+
builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
|
|
370
|
+
state.posts = action.payload;
|
|
371
|
+
});
|
|
372
|
+
builder.addCase<Error>(fetchPosts.failure, (state, action) => {
|
|
373
|
+
state.error = action.payload;
|
|
374
|
+
});
|
|
375
|
+
})
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Returns **any** State of reducer has immer
|
|
379
|
+
|
|
380
|
+
## unfoldSaga
|
|
381
|
+
|
|
382
|
+
Common saga helper that unifies handling side effects into only one standard form
|
|
383
|
+
|
|
384
|
+
### Parameters
|
|
385
|
+
|
|
386
|
+
- `body` **UnfoldSagaHandlerType** 
|
|
387
|
+
- `body.action` **UnfoldSagaActionType** Action
|
|
388
|
+
- `body.handler` **[Function][26]** Main handler function. Its returned value will become onSuccess callback param
|
|
389
|
+
|
|
390
|
+
### Examples
|
|
391
|
+
|
|
392
|
+
```javascript
|
|
393
|
+
import {SagaIterator} from 'redux-saga';
|
|
394
|
+
import {call, takeLatest} from 'redux-saga/effects';
|
|
395
|
+
import {unfoldSaga} from 'redux-unfold-saga-toolkit';
|
|
396
|
+
import {fetchPosts} from './action';
|
|
397
|
+
|
|
398
|
+
// Saga function
|
|
399
|
+
function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
|
|
400
|
+
yield unfoldSaga({
|
|
401
|
+
action: action,
|
|
402
|
+
handler: function* () {
|
|
403
|
+
const data = yield call(ApiPost.listPost);
|
|
404
|
+
return data;
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
or
|
|
410
|
+
|
|
411
|
+
// Async function
|
|
412
|
+
function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
|
|
413
|
+
yield unfoldSaga({
|
|
414
|
+
action: action,
|
|
415
|
+
handler: async function () {
|
|
416
|
+
const data = await ApiPost.listPost();
|
|
417
|
+
return data;
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function* defaultSaga() {
|
|
423
|
+
yield takeLatest('FETCH_POSTS', takeQueryPosts);
|
|
424
|
+
// yield takeLatest(fetchPosts, takeQueryPosts);
|
|
425
|
+
// yield takeLatest(fetchPosts.type, takeQueryPosts);
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Returns **SagaIterator** SagaIterator
|
|
430
|
+
|
|
431
|
+
[1]: #createactiontypeonbegin
|
|
432
|
+
[2]: #parameters
|
|
433
|
+
[3]: #examples
|
|
434
|
+
[4]: #createactiontypeonfinish
|
|
435
|
+
[5]: #parameters-1
|
|
436
|
+
[6]: #examples-1
|
|
437
|
+
[7]: #createactiontypeonsuccess
|
|
438
|
+
[8]: #parameters-2
|
|
439
|
+
[9]: #examples-2
|
|
440
|
+
[10]: #createactiontypeonfailure
|
|
441
|
+
[11]: #parameters-3
|
|
442
|
+
[12]: #examples-3
|
|
443
|
+
[13]: #createaction
|
|
444
|
+
[14]: #parameters-4
|
|
445
|
+
[15]: #examples-4
|
|
446
|
+
[16]: #createstoreaction
|
|
447
|
+
[17]: #parameters-5
|
|
448
|
+
[18]: #examples-5
|
|
449
|
+
[19]: #createreducer
|
|
450
|
+
[20]: #parameters-6
|
|
451
|
+
[21]: #examples-6
|
|
452
|
+
[22]: #unfoldsaga
|
|
453
|
+
[23]: #parameters-7
|
|
454
|
+
[24]: #examples-7
|
|
455
|
+
[25]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
|
456
|
+
[26]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function
|
|
457
|
+
|
|
458
|
+
## License
|
|
459
|
+
|
|
460
|
+
MIT © [hungnguyen2809](https://github.com/hungnguyen2809)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { UnfoldSagaActionCreator, UnfoldSagaStoreActionCreator } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* @param key
|
|
4
|
+
* @returns {string} `${key}_BEGAN`
|
|
5
|
+
* @description Create onBegin action type
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* import {createActionTypeOnBegin} from 'redux-unfold-saga-toolkit';
|
|
9
|
+
*
|
|
10
|
+
* createActionTypeOnBegin('DO_SOMETHING') // DO_SOMETHING_BEGAN
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare function createActionTypeOnBegin(key: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* @param key
|
|
16
|
+
* @returns {string} `${key}_FINISHED`
|
|
17
|
+
* @description Create onFinish action type
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import {createActionTypeOnFinish} from 'redux-unfold-saga-toolkit';
|
|
21
|
+
*
|
|
22
|
+
* createActionTypeOnFinish('DO_SOMETHING') // DO_SOMETHING_FINISHED
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export declare function createActionTypeOnFinish(key: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* @param key
|
|
28
|
+
* @returns {string} `${key}_SUCCEEDED`
|
|
29
|
+
* @description Create onSuccess action type
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import {createActionTypeOnSuccess} from 'redux-unfold-saga-toolkit';
|
|
33
|
+
*
|
|
34
|
+
* createActionTypeOnSuccess('DO_SOMETHING') // DO_SOMETHING_SUCCEEDED
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function createActionTypeOnSuccess(key: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* @param key
|
|
40
|
+
* @returns {string} `${key}_FAILED`
|
|
41
|
+
* @description Create onFailure action type
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* import {createActionTypeOnFailure} from 'redux-unfold-saga-toolkit';
|
|
45
|
+
*
|
|
46
|
+
* createActionTypeOnFailure('DO_SOMETHING') // DO_SOMETHING_FAILED
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function createActionTypeOnFailure(key: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* @param type
|
|
52
|
+
* @returns {UnfoldSagaActionCreator} action
|
|
53
|
+
* @description Create an action for real life usage inside or even outside of a component, no dispatch to reducer
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import {createAction} from 'redux-unfold-saga-toolkit';
|
|
57
|
+
*
|
|
58
|
+
* const fetchPosts = createAction<IPostPayload>('FETCH_POSTS');
|
|
59
|
+
*
|
|
60
|
+
* dispatch(
|
|
61
|
+
* fetchPosts(
|
|
62
|
+
* {category: 'HOT'},
|
|
63
|
+
* {
|
|
64
|
+
* onBegin: () => {
|
|
65
|
+
* // Do something before the query
|
|
66
|
+
* setLoading(true);
|
|
67
|
+
* },
|
|
68
|
+
* onFailure: (error: Error) => {
|
|
69
|
+
* // Do something in case of caught error
|
|
70
|
+
* },
|
|
71
|
+
* onSuccess: (posts: IPost[]) => {
|
|
72
|
+
* // Do something after the query succeeded
|
|
73
|
+
* },
|
|
74
|
+
* onFinish: () => {
|
|
75
|
+
* // Do something after everything is done
|
|
76
|
+
* setLoading(false);
|
|
77
|
+
* },
|
|
78
|
+
* },
|
|
79
|
+
* ),
|
|
80
|
+
* );
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function createAction<PayloadType = any>(type: string): UnfoldSagaActionCreator<PayloadType>;
|
|
84
|
+
/**
|
|
85
|
+
* @param type
|
|
86
|
+
* @returns {UnfoldSagaActionCreator} action
|
|
87
|
+
* @description Create an action for real life usage inside or even outside of a component, dispatch to reducer with automatic create action type
|
|
88
|
+
* @example
|
|
89
|
+
* // Action
|
|
90
|
+
* ```ts
|
|
91
|
+
* import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
|
|
92
|
+
*
|
|
93
|
+
* const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
|
|
94
|
+
*
|
|
95
|
+
* dispatch(
|
|
96
|
+
* fetchPosts(
|
|
97
|
+
* {category: 'HOT'},
|
|
98
|
+
* {
|
|
99
|
+
* onBegin: () => {
|
|
100
|
+
* // Do something before the query
|
|
101
|
+
* setLoading(true);
|
|
102
|
+
* },
|
|
103
|
+
* onFailure: (error:Error) => {
|
|
104
|
+
* // Do something in case of caught error
|
|
105
|
+
* },
|
|
106
|
+
* onSuccess: (posts:IPost) => {
|
|
107
|
+
* // Do something after the query succeeded
|
|
108
|
+
* },
|
|
109
|
+
* onFinish: () => {
|
|
110
|
+
* // Do something after everything is done
|
|
111
|
+
* setLoading(false);
|
|
112
|
+
* },
|
|
113
|
+
* },
|
|
114
|
+
* ),
|
|
115
|
+
* );
|
|
116
|
+
*
|
|
117
|
+
* // Reducer
|
|
118
|
+
* const initState = {
|
|
119
|
+
* posts: [],
|
|
120
|
+
* error: null,
|
|
121
|
+
* loading: true,
|
|
122
|
+
* };
|
|
123
|
+
*
|
|
124
|
+
* const postReducer = createReducer(initState, (builder) => {
|
|
125
|
+
* builder.addCase<void>(fetchPosts.begin, (state, action) => {
|
|
126
|
+
* state.loading = true;
|
|
127
|
+
* });
|
|
128
|
+
* builder.addCase<void>(fetchPosts.finish, (state, action) => {
|
|
129
|
+
* state.loading = false;
|
|
130
|
+
* });
|
|
131
|
+
* builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
|
|
132
|
+
* state.posts = action.payload;
|
|
133
|
+
* });
|
|
134
|
+
* builder.addCase<Error>(fetchPosts.failure, (state, action) => {
|
|
135
|
+
* state.error = action.payload;
|
|
136
|
+
* });
|
|
137
|
+
* })
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export declare function createStoreAction<PayloadType = any>(type: string): UnfoldSagaStoreActionCreator<PayloadType>;
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param key
|
|
3
|
+
* @returns {string} `${key}_BEGAN`
|
|
4
|
+
* @description Create onBegin action type
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* import {createActionTypeOnBegin} from 'redux-unfold-saga-toolkit';
|
|
8
|
+
*
|
|
9
|
+
* createActionTypeOnBegin('DO_SOMETHING') // DO_SOMETHING_BEGAN
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function createActionTypeOnBegin(key) {
|
|
13
|
+
return `${key}_BEGAN`;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* @param key
|
|
17
|
+
* @returns {string} `${key}_FINISHED`
|
|
18
|
+
* @description Create onFinish action type
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import {createActionTypeOnFinish} from 'redux-unfold-saga-toolkit';
|
|
22
|
+
*
|
|
23
|
+
* createActionTypeOnFinish('DO_SOMETHING') // DO_SOMETHING_FINISHED
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export function createActionTypeOnFinish(key) {
|
|
27
|
+
return `${key}_FINISHED`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* @param key
|
|
31
|
+
* @returns {string} `${key}_SUCCEEDED`
|
|
32
|
+
* @description Create onSuccess action type
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* import {createActionTypeOnSuccess} from 'redux-unfold-saga-toolkit';
|
|
36
|
+
*
|
|
37
|
+
* createActionTypeOnSuccess('DO_SOMETHING') // DO_SOMETHING_SUCCEEDED
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function createActionTypeOnSuccess(key) {
|
|
41
|
+
return `${key}_SUCCEEDED`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @param key
|
|
45
|
+
* @returns {string} `${key}_FAILED`
|
|
46
|
+
* @description Create onFailure action type
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import {createActionTypeOnFailure} from 'redux-unfold-saga-toolkit';
|
|
50
|
+
*
|
|
51
|
+
* createActionTypeOnFailure('DO_SOMETHING') // DO_SOMETHING_FAILED
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function createActionTypeOnFailure(key) {
|
|
55
|
+
return `${key}_FAILED`;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @param type
|
|
59
|
+
* @returns {UnfoldSagaActionCreator} action
|
|
60
|
+
* @description Create an action for real life usage inside or even outside of a component, no dispatch to reducer
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* import {createAction} from 'redux-unfold-saga-toolkit';
|
|
64
|
+
*
|
|
65
|
+
* const fetchPosts = createAction<IPostPayload>('FETCH_POSTS');
|
|
66
|
+
*
|
|
67
|
+
* dispatch(
|
|
68
|
+
* fetchPosts(
|
|
69
|
+
* {category: 'HOT'},
|
|
70
|
+
* {
|
|
71
|
+
* onBegin: () => {
|
|
72
|
+
* // Do something before the query
|
|
73
|
+
* setLoading(true);
|
|
74
|
+
* },
|
|
75
|
+
* onFailure: (error: Error) => {
|
|
76
|
+
* // Do something in case of caught error
|
|
77
|
+
* },
|
|
78
|
+
* onSuccess: (posts: IPost[]) => {
|
|
79
|
+
* // Do something after the query succeeded
|
|
80
|
+
* },
|
|
81
|
+
* onFinish: () => {
|
|
82
|
+
* // Do something after everything is done
|
|
83
|
+
* setLoading(false);
|
|
84
|
+
* },
|
|
85
|
+
* },
|
|
86
|
+
* ),
|
|
87
|
+
* );
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
export function createAction(type) {
|
|
91
|
+
function actionCreator(payload, callbacks = {}) {
|
|
92
|
+
return {
|
|
93
|
+
callbacks,
|
|
94
|
+
options: {},
|
|
95
|
+
payload,
|
|
96
|
+
type,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
actionCreator.type = type;
|
|
100
|
+
actionCreator.toString = () => type;
|
|
101
|
+
return actionCreator;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* @param type
|
|
105
|
+
* @returns {UnfoldSagaActionCreator} action
|
|
106
|
+
* @description Create an action for real life usage inside or even outside of a component, dispatch to reducer with automatic create action type
|
|
107
|
+
* @example
|
|
108
|
+
* // Action
|
|
109
|
+
* ```ts
|
|
110
|
+
* import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
|
|
111
|
+
*
|
|
112
|
+
* const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
|
|
113
|
+
*
|
|
114
|
+
* dispatch(
|
|
115
|
+
* fetchPosts(
|
|
116
|
+
* {category: 'HOT'},
|
|
117
|
+
* {
|
|
118
|
+
* onBegin: () => {
|
|
119
|
+
* // Do something before the query
|
|
120
|
+
* setLoading(true);
|
|
121
|
+
* },
|
|
122
|
+
* onFailure: (error:Error) => {
|
|
123
|
+
* // Do something in case of caught error
|
|
124
|
+
* },
|
|
125
|
+
* onSuccess: (posts:IPost) => {
|
|
126
|
+
* // Do something after the query succeeded
|
|
127
|
+
* },
|
|
128
|
+
* onFinish: () => {
|
|
129
|
+
* // Do something after everything is done
|
|
130
|
+
* setLoading(false);
|
|
131
|
+
* },
|
|
132
|
+
* },
|
|
133
|
+
* ),
|
|
134
|
+
* );
|
|
135
|
+
*
|
|
136
|
+
* // Reducer
|
|
137
|
+
* const initState = {
|
|
138
|
+
* posts: [],
|
|
139
|
+
* error: null,
|
|
140
|
+
* loading: true,
|
|
141
|
+
* };
|
|
142
|
+
*
|
|
143
|
+
* const postReducer = createReducer(initState, (builder) => {
|
|
144
|
+
* builder.addCase<void>(fetchPosts.begin, (state, action) => {
|
|
145
|
+
* state.loading = true;
|
|
146
|
+
* });
|
|
147
|
+
* builder.addCase<void>(fetchPosts.finish, (state, action) => {
|
|
148
|
+
* state.loading = false;
|
|
149
|
+
* });
|
|
150
|
+
* builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
|
|
151
|
+
* state.posts = action.payload;
|
|
152
|
+
* });
|
|
153
|
+
* builder.addCase<Error>(fetchPosts.failure, (state, action) => {
|
|
154
|
+
* state.error = action.payload;
|
|
155
|
+
* });
|
|
156
|
+
* })
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
export function createStoreAction(type) {
|
|
160
|
+
function actionCreator(payload, callbacks = {}) {
|
|
161
|
+
return {
|
|
162
|
+
callbacks,
|
|
163
|
+
options: { stateful: true },
|
|
164
|
+
payload,
|
|
165
|
+
type,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
actionCreator.type = type;
|
|
169
|
+
actionCreator.toString = () => type;
|
|
170
|
+
actionCreator.begin = createActionTypeOnBegin(type);
|
|
171
|
+
actionCreator.finish = createActionTypeOnFinish(type);
|
|
172
|
+
actionCreator.success = createActionTypeOnSuccess(type);
|
|
173
|
+
actionCreator.failure = createActionTypeOnFailure(type);
|
|
174
|
+
return actionCreator;
|
|
175
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { SagaIterator } from 'redux-saga';
|
|
2
|
+
import { UnfoldSagaHandlerType } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* @param {UnfoldSagaHandlerType} body
|
|
5
|
+
* @param {UnfoldSagaActionType} body.action Action
|
|
6
|
+
* @param {Function} body.handler Main handler function. Its returned value will become onSuccess callback param
|
|
7
|
+
* @returns {SagaIterator} SagaIterator
|
|
8
|
+
* @description Common saga helper that unifies handling side effects into only one standard form
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import {SagaIterator} from 'redux-saga';
|
|
12
|
+
* import {call, takeLatest} from 'redux-saga/effects';
|
|
13
|
+
* import {unfoldSaga} from 'redux-unfold-saga-toolkit';
|
|
14
|
+
* import {fetchPosts} from './action';
|
|
15
|
+
*
|
|
16
|
+
* // Saga function
|
|
17
|
+
* function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
|
|
18
|
+
* yield unfoldSaga({
|
|
19
|
+
* action: action,
|
|
20
|
+
* handler: function* () {
|
|
21
|
+
* const data = yield call(ApiPost.listPost);
|
|
22
|
+
* return data;
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* or
|
|
28
|
+
*
|
|
29
|
+
* // Async function
|
|
30
|
+
* function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
|
|
31
|
+
* yield unfoldSaga({
|
|
32
|
+
* action: action,
|
|
33
|
+
* handler: async function () {
|
|
34
|
+
* const data = await ApiPost.listPost();
|
|
35
|
+
* return data;
|
|
36
|
+
* },
|
|
37
|
+
* });
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* function* defaultSaga() {
|
|
41
|
+
* yield takeLatest('FETCH_POSTS', takeQueryPosts);
|
|
42
|
+
* // yield takeLatest(fetchPosts, takeQueryPosts);
|
|
43
|
+
* // yield takeLatest(fetchPosts.type, takeQueryPosts);
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function unfoldSaga({ action, handler }: UnfoldSagaHandlerType): SagaIterator;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { call, put } from 'redux-saga/effects';
|
|
2
|
+
import { createActionTypeOnBegin, createActionTypeOnFailure, createActionTypeOnFinish, createActionTypeOnSuccess, } from '../action';
|
|
3
|
+
import { noop } from '../helper';
|
|
4
|
+
/**
|
|
5
|
+
* @param {UnfoldSagaHandlerType} body
|
|
6
|
+
* @param {UnfoldSagaActionType} body.action Action
|
|
7
|
+
* @param {Function} body.handler Main handler function. Its returned value will become onSuccess callback param
|
|
8
|
+
* @returns {SagaIterator} SagaIterator
|
|
9
|
+
* @description Common saga helper that unifies handling side effects into only one standard form
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {SagaIterator} from 'redux-saga';
|
|
13
|
+
* import {call, takeLatest} from 'redux-saga/effects';
|
|
14
|
+
* import {unfoldSaga} from 'redux-unfold-saga-toolkit';
|
|
15
|
+
* import {fetchPosts} from './action';
|
|
16
|
+
*
|
|
17
|
+
* // Saga function
|
|
18
|
+
* function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
|
|
19
|
+
* yield unfoldSaga({
|
|
20
|
+
* action: action,
|
|
21
|
+
* handler: function* () {
|
|
22
|
+
* const data = yield call(ApiPost.listPost);
|
|
23
|
+
* return data;
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* or
|
|
29
|
+
*
|
|
30
|
+
* // Async function
|
|
31
|
+
* function* takeQueryPosts(action: UnfoldSagaActionType): Iterable<SagaIterator> {
|
|
32
|
+
* yield unfoldSaga({
|
|
33
|
+
* action: action,
|
|
34
|
+
* handler: async function () {
|
|
35
|
+
* const data = await ApiPost.listPost();
|
|
36
|
+
* return data;
|
|
37
|
+
* },
|
|
38
|
+
* });
|
|
39
|
+
* }
|
|
40
|
+
*
|
|
41
|
+
* function* defaultSaga() {
|
|
42
|
+
* yield takeLatest('FETCH_POSTS', takeQueryPosts);
|
|
43
|
+
* // yield takeLatest(fetchPosts, takeQueryPosts);
|
|
44
|
+
* // yield takeLatest(fetchPosts.type, takeQueryPosts);
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function* unfoldSaga({ action, handler }) {
|
|
49
|
+
let data;
|
|
50
|
+
const defaultCallbacks = {
|
|
51
|
+
onBegin: noop,
|
|
52
|
+
onFinish: noop,
|
|
53
|
+
onSuccess: noop,
|
|
54
|
+
onFailure: noop,
|
|
55
|
+
};
|
|
56
|
+
const defaultOptions = {
|
|
57
|
+
stateful: false,
|
|
58
|
+
};
|
|
59
|
+
Object.assign(defaultCallbacks, action.callbacks);
|
|
60
|
+
Object.assign(defaultOptions, action.options);
|
|
61
|
+
try {
|
|
62
|
+
if (defaultOptions.stateful)
|
|
63
|
+
yield put({ type: createActionTypeOnBegin(action.type) });
|
|
64
|
+
yield call(defaultCallbacks.onBegin);
|
|
65
|
+
if (['GeneratorFunction', 'AsyncGeneratorFunction'].includes(handler.constructor.name)) {
|
|
66
|
+
data = yield* handler();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
data = yield call(handler);
|
|
70
|
+
}
|
|
71
|
+
if (defaultOptions.stateful)
|
|
72
|
+
yield put({ type: createActionTypeOnSuccess(action.type), payload: data });
|
|
73
|
+
yield call(defaultCallbacks.onSuccess, data);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (defaultOptions.stateful)
|
|
77
|
+
yield put({ type: createActionTypeOnFailure(action.type), payload: error });
|
|
78
|
+
yield call(defaultCallbacks.onFailure, error);
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
if (defaultOptions.stateful)
|
|
82
|
+
yield put({ type: createActionTypeOnFinish(action.type) });
|
|
83
|
+
yield call(defaultCallbacks.onFinish);
|
|
84
|
+
}
|
|
85
|
+
return data;
|
|
86
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { UnfoldSagaActionReducerMapBuilder, UnfoldSagaActionType } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
*
|
|
4
|
+
* @param initialState
|
|
5
|
+
* @param builderCallback
|
|
6
|
+
* @returns State of reducer has immer
|
|
7
|
+
* @description A utility function that allows defining a reducer as a mapping from action
|
|
8
|
+
* type to *case reducer* functions that handle these action types. The
|
|
9
|
+
* reducer's initial state is passed as the first argument.
|
|
10
|
+
* @example
|
|
11
|
+
* // Action
|
|
12
|
+
* ```ts
|
|
13
|
+
* import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
|
|
14
|
+
*
|
|
15
|
+
* const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
|
|
16
|
+
*
|
|
17
|
+
* dispatch(
|
|
18
|
+
* fetchPosts(
|
|
19
|
+
* {category: 'HOT'},
|
|
20
|
+
* {
|
|
21
|
+
* onBegin: () => {
|
|
22
|
+
* // Do something before the query
|
|
23
|
+
* setLoading(true);
|
|
24
|
+
* },
|
|
25
|
+
* onFailure: (error:Error) => {
|
|
26
|
+
* // Do something in case of caught error
|
|
27
|
+
* },
|
|
28
|
+
* onSuccess: (posts:IPost) => {
|
|
29
|
+
* // Do something after the query succeeded
|
|
30
|
+
* },
|
|
31
|
+
* onFinish: () => {
|
|
32
|
+
* // Do something after everything is done
|
|
33
|
+
* setLoading(false);
|
|
34
|
+
* },
|
|
35
|
+
* },
|
|
36
|
+
* ),
|
|
37
|
+
* );
|
|
38
|
+
*
|
|
39
|
+
* // Reducer
|
|
40
|
+
* const initState = {
|
|
41
|
+
* posts: [],
|
|
42
|
+
* error: null,
|
|
43
|
+
* loading: true,
|
|
44
|
+
* };
|
|
45
|
+
*
|
|
46
|
+
* const postReducer = createReducer(initState, (builder) => {
|
|
47
|
+
* builder.addCase<void>(fetchPosts.begin, (state, action) => {
|
|
48
|
+
* state.loading = true;
|
|
49
|
+
* });
|
|
50
|
+
* builder.addCase<void>(fetchPosts.finish, (state, action) => {
|
|
51
|
+
* state.loading = false;
|
|
52
|
+
* });
|
|
53
|
+
* builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
|
|
54
|
+
* state.posts = action.payload;
|
|
55
|
+
* });
|
|
56
|
+
* builder.addCase<Error>(fetchPosts.failure, (state, action) => {
|
|
57
|
+
* state.error = action.payload;
|
|
58
|
+
* });
|
|
59
|
+
* })
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function createReducer<S = any>(initialState: S, builderCallback: (builder: UnfoldSagaActionReducerMapBuilder<S>) => void): (state: S | undefined, action: UnfoldSagaActionType) => S;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { produce } from 'immer';
|
|
2
|
+
function executeReducerBuilderCallback(builderCallback) {
|
|
3
|
+
const actionsMap = {};
|
|
4
|
+
const builder = {
|
|
5
|
+
addCase: (action, reducer) => {
|
|
6
|
+
const type = typeof action === 'string' ? action : action.type;
|
|
7
|
+
actionsMap[type] = reducer;
|
|
8
|
+
return builder;
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
builderCallback(builder);
|
|
12
|
+
return actionsMap;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param initialState
|
|
17
|
+
* @param builderCallback
|
|
18
|
+
* @returns State of reducer has immer
|
|
19
|
+
* @description A utility function that allows defining a reducer as a mapping from action
|
|
20
|
+
* type to *case reducer* functions that handle these action types. The
|
|
21
|
+
* reducer's initial state is passed as the first argument.
|
|
22
|
+
* @example
|
|
23
|
+
* // Action
|
|
24
|
+
* ```ts
|
|
25
|
+
* import {createStoreAction, createReducer} from 'redux-unfold-saga-toolkit';
|
|
26
|
+
*
|
|
27
|
+
* const fetchPosts = createStoreAction<IPostPayload>('FETCH_POSTS');
|
|
28
|
+
*
|
|
29
|
+
* dispatch(
|
|
30
|
+
* fetchPosts(
|
|
31
|
+
* {category: 'HOT'},
|
|
32
|
+
* {
|
|
33
|
+
* onBegin: () => {
|
|
34
|
+
* // Do something before the query
|
|
35
|
+
* setLoading(true);
|
|
36
|
+
* },
|
|
37
|
+
* onFailure: (error:Error) => {
|
|
38
|
+
* // Do something in case of caught error
|
|
39
|
+
* },
|
|
40
|
+
* onSuccess: (posts:IPost) => {
|
|
41
|
+
* // Do something after the query succeeded
|
|
42
|
+
* },
|
|
43
|
+
* onFinish: () => {
|
|
44
|
+
* // Do something after everything is done
|
|
45
|
+
* setLoading(false);
|
|
46
|
+
* },
|
|
47
|
+
* },
|
|
48
|
+
* ),
|
|
49
|
+
* );
|
|
50
|
+
*
|
|
51
|
+
* // Reducer
|
|
52
|
+
* const initState = {
|
|
53
|
+
* posts: [],
|
|
54
|
+
* error: null,
|
|
55
|
+
* loading: true,
|
|
56
|
+
* };
|
|
57
|
+
*
|
|
58
|
+
* const postReducer = createReducer(initState, (builder) => {
|
|
59
|
+
* builder.addCase<void>(fetchPosts.begin, (state, action) => {
|
|
60
|
+
* state.loading = true;
|
|
61
|
+
* });
|
|
62
|
+
* builder.addCase<void>(fetchPosts.finish, (state, action) => {
|
|
63
|
+
* state.loading = false;
|
|
64
|
+
* });
|
|
65
|
+
* builder.addCase<IPost[]>(fetchPosts.success, (state, action) => {
|
|
66
|
+
* state.posts = action.payload;
|
|
67
|
+
* });
|
|
68
|
+
* builder.addCase<Error>(fetchPosts.failure, (state, action) => {
|
|
69
|
+
* state.error = action.payload;
|
|
70
|
+
* });
|
|
71
|
+
* })
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export function createReducer(initialState, builderCallback) {
|
|
75
|
+
const actionsMap = executeReducerBuilderCallback(builderCallback);
|
|
76
|
+
return function reducer(state = initialState, action) {
|
|
77
|
+
const caseReducer = actionsMap[action.type];
|
|
78
|
+
return produce(state, (draft) => {
|
|
79
|
+
if (caseReducer)
|
|
80
|
+
caseReducer(draft, action);
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Draft } from 'immer';
|
|
2
|
+
import { AnyAction } from 'redux-saga';
|
|
3
|
+
export interface UnfoldSagaCallbacksType {
|
|
4
|
+
onBegin?: Function;
|
|
5
|
+
onFinish?: Function;
|
|
6
|
+
onSuccess?: Function;
|
|
7
|
+
onFailure?: Function;
|
|
8
|
+
}
|
|
9
|
+
export interface UnfoldSagaOptionsType {
|
|
10
|
+
stateful?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface UnfoldSagaHandlerType {
|
|
13
|
+
action: UnfoldSagaActionType;
|
|
14
|
+
handler: Function | GeneratorFunction;
|
|
15
|
+
}
|
|
16
|
+
export interface UnfoldSagaPayloadAction<T = any> {
|
|
17
|
+
type: string;
|
|
18
|
+
payload: T;
|
|
19
|
+
}
|
|
20
|
+
export type Fn = (...args: any[]) => any;
|
|
21
|
+
export type UnfoldSagaCaseReducer<S, P = any> = (state: Draft<S>, action: UnfoldSagaPayloadAction<P>) => void;
|
|
22
|
+
export type UnfoldSagaActionReducer = UnfoldSagaActionCreator | string;
|
|
23
|
+
export interface UnfoldSagaActionReducerMapBuilder<S> {
|
|
24
|
+
addCase: <P = any>(action: UnfoldSagaActionReducer, reducer: UnfoldSagaCaseReducer<S, P>) => UnfoldSagaActionReducerMapBuilder<S>;
|
|
25
|
+
}
|
|
26
|
+
export interface UnfoldSagaActionType<PayloadType = any> extends AnyAction {
|
|
27
|
+
payload: PayloadType;
|
|
28
|
+
options: UnfoldSagaOptionsType;
|
|
29
|
+
callbacks: UnfoldSagaCallbacksType;
|
|
30
|
+
}
|
|
31
|
+
export interface UnfoldSagaActionCreator<PayloadType = any> {
|
|
32
|
+
(payload?: PayloadType, callbacks?: UnfoldSagaCallbacksType): UnfoldSagaActionType;
|
|
33
|
+
type: string;
|
|
34
|
+
}
|
|
35
|
+
export interface UnfoldSagaStoreActionCreator<PayloadType = any> extends UnfoldSagaActionCreator<PayloadType> {
|
|
36
|
+
begin: string;
|
|
37
|
+
finish: string;
|
|
38
|
+
success: string;
|
|
39
|
+
failure: string;
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "redux-unfold-saga-toolkit",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A no headache middleware helper for redux-saga.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist/"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"commit": "git cz",
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"prepare": "husky"
|
|
17
|
+
},
|
|
18
|
+
"author": {
|
|
19
|
+
"name": "Hung Nguyen",
|
|
20
|
+
"email": "hungnguyen.dev99@gmail.com",
|
|
21
|
+
"url": "https://github.com/hungnguyen2809"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/hungnguyen2809/redux-unfold-saga-toolkit.git"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"registry": "https://registry.npmjs.org/",
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"redux",
|
|
33
|
+
"redux-saga",
|
|
34
|
+
"redux-unfold-saga",
|
|
35
|
+
"redux-unfold-saga-toolkit"
|
|
36
|
+
],
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"immer": ">=10.0.0",
|
|
39
|
+
"redux-saga": "*"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@commitlint/cli": "^20.2.0",
|
|
43
|
+
"@commitlint/config-conventional": "^20.2.0",
|
|
44
|
+
"@eslint/js": "^9.39.1",
|
|
45
|
+
"@types/jest": "^30.0.0",
|
|
46
|
+
"commitizen": "^4.3.1",
|
|
47
|
+
"eslint": "^9.39.1",
|
|
48
|
+
"git-cz": "^4.9.0",
|
|
49
|
+
"husky": "^9.1.7",
|
|
50
|
+
"immer": "10.0.0",
|
|
51
|
+
"jest": "^30.2.0",
|
|
52
|
+
"lint-staged": "^16.2.7",
|
|
53
|
+
"prettier": "^3.7.4",
|
|
54
|
+
"redux-saga": "1.4.2",
|
|
55
|
+
"ts-jest": "^29.4.6",
|
|
56
|
+
"typescript": "^5.9.3",
|
|
57
|
+
"typescript-eslint": "^8.48.1"
|
|
58
|
+
},
|
|
59
|
+
"lint-staged": {
|
|
60
|
+
"*.{js,jsx,ts,tsx}": [
|
|
61
|
+
"eslint --fix",
|
|
62
|
+
"prettier --write"
|
|
63
|
+
],
|
|
64
|
+
"*.{json,md,yml}": [
|
|
65
|
+
"prettier --write"
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"config": {
|
|
69
|
+
"commitizen": {
|
|
70
|
+
"path": "git-cz"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|