1
1
import {
2
- ActionCreatorWithOptionalPayload ,
3
- ActionCreatorWithoutPayload ,
4
- ActionCreatorWithPreparedPayload ,
2
+ type ActionCreatorWithOptionalPayload ,
3
+ type ActionCreatorWithoutPayload ,
4
+ type ActionCreatorWithPreparedPayload ,
5
5
createAction
6
6
} from '@reduxjs/toolkit' ;
7
- import * as Sentry from '@sentry/browser' ;
8
- import { SagaIterator } from 'redux-saga' ;
9
- import { StrictEffect , takeEvery } from 'redux-saga/effects' ;
7
+ import type { SagaIterator } from 'redux-saga' ;
8
+ import { type StrictEffect , takeEvery , takeLatest , takeLeading } from 'redux-saga/effects' ;
9
+
10
+ import { safeTakeEvery , wrapSaga } from '../sagas/SafeEffects' ;
11
+ import type { SourceActionType } from '../utils/ActionsHelper' ;
12
+ import { type ActionTypeToCreator , objectEntries } from '../utils/TypeHelper' ;
10
13
11
14
/**
12
15
* Creates actions, given a base name and base actions
13
16
* @param baseName The base name of the actions
14
- * @param baseActions The base actions. Use a falsy value to create an action without a payload.
17
+ * @param baseActions The base actions. Use a non function value to create an action without a payload.
15
18
* @returns An object containing the actions
16
19
*/
17
20
export function createActions < BaseName extends string , BaseActions extends Record < string , any > > (
@@ -21,11 +24,12 @@ export function createActions<BaseName extends string, BaseActions extends Recor
21
24
return Object . entries ( baseActions ) . reduce (
22
25
( res , [ name , func ] ) => ( {
23
26
...res ,
24
- [ name ] : func
25
- ? createAction ( `${ baseName } /${ name } ` , ( ...args : any ) => ( { payload : func ( ...args ) } ) )
26
- : createAction ( `${ baseName } /${ name } ` )
27
+ [ name ] :
28
+ typeof func === 'function'
29
+ ? createAction ( `${ baseName } /${ name } ` , ( ...args : any ) => ( { payload : func ( ...args ) } ) )
30
+ : createAction ( `${ baseName } /${ name } ` )
27
31
} ) ,
28
- { } as {
32
+ { } as Readonly < {
29
33
[ K in keyof BaseActions ] : K extends string
30
34
? BaseActions [ K ] extends ( ...args : any ) => any
31
35
? ActionCreatorWithPreparedPayload <
@@ -35,35 +39,40 @@ export function createActions<BaseName extends string, BaseActions extends Recor
35
39
>
36
40
: ActionCreatorWithoutPayload < `${BaseName } /${K } `>
37
41
: never ;
38
- }
42
+ } >
39
43
) ;
40
44
}
41
45
42
- export function combineSagaHandlers <
43
- TActions extends Record <
44
- string ,
45
- ActionCreatorWithPreparedPayload < any , any > | ActionCreatorWithoutPayload < any >
46
- >
47
- > (
48
- actions : TActions ,
49
- handlers : {
50
- // TODO: Maybe this can be stricter? And remove the optional type after
51
- // migration is fully done
52
- [ K in keyof TActions ] ?: ( action : ReturnType < TActions [ K ] > ) => SagaIterator ;
53
- } ,
54
- others ?: ( takeEvery : typeof saferTakeEvery ) => SagaIterator
55
- ) : ( ) => SagaIterator {
56
- const sagaHandlers = Object . entries ( handlers ) . map ( ( [ actionName , saga ] ) =>
57
- saferTakeEvery ( actions [ actionName ] , saga )
58
- ) ;
46
+ type SagaHandler < T extends SourceActionType [ 'type' ] > = (
47
+ action : ReturnType < ActionTypeToCreator < T > >
48
+ ) => Generator < StrictEffect > ;
49
+
50
+ type SagaHandlers = {
51
+ [ K in SourceActionType [ 'type' ] ] ?:
52
+ | SagaHandler < K >
53
+ | Partial < Record < 'takeEvery' | 'takeLatest' | 'takeLeading' , SagaHandler < K > > > ;
54
+ } ;
55
+
56
+ export function combineSagaHandlers ( handlers : SagaHandlers ) {
59
57
return function * ( ) : SagaIterator {
60
- yield * sagaHandlers ;
61
- if ( others ) {
62
- const obj = others ( saferTakeEvery ) ;
63
- while ( true ) {
64
- const { done, value } = obj . next ( ) ;
65
- if ( done ) break ;
66
- yield value ;
58
+ for ( const [ actionName , saga ] of objectEntries ( handlers ) ) {
59
+ if ( saga === undefined ) {
60
+ continue ;
61
+ } else if ( typeof saga === 'function' ) {
62
+ yield takeEvery ( actionName , wrapSaga ( saga ) ) ;
63
+ continue ;
64
+ }
65
+
66
+ if ( saga . takeEvery ) {
67
+ yield takeEvery ( actionName , wrapSaga ( saga . takeEvery ) ) ;
68
+ }
69
+
70
+ if ( saga . takeLeading ) {
71
+ yield takeLeading ( actionName , wrapSaga ( saga . takeLeading ) ) ;
72
+ }
73
+
74
+ if ( saga . takeLatest ) {
75
+ yield takeLatest ( actionName , wrapSaga ( saga . takeLatest ) ) ;
67
76
}
68
77
}
69
78
} ;
@@ -74,26 +83,5 @@ export function saferTakeEvery<
74
83
| ActionCreatorWithOptionalPayload < any >
75
84
| ActionCreatorWithPreparedPayload < any [ ] , any >
76
85
> ( actionPattern : Action , fn : ( action : ReturnType < Action > ) => Generator < StrictEffect < any > > ) {
77
- function * wrapper ( action : ReturnType < Action > ) {
78
- try {
79
- yield * fn ( action ) ;
80
- } catch ( error ) {
81
- handleUncaughtError ( error ) ;
82
- }
83
- }
84
-
85
- return takeEvery ( actionPattern . type , wrapper ) ;
86
- }
87
-
88
- function handleUncaughtError ( error : any ) {
89
- if ( process . env . NODE_ENV === 'development' ) {
90
- // react-error-overlay is a "special" package that's automatically included
91
- // in development mode by CRA
92
-
93
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
94
- // @ts -ignore
95
- import ( 'react-error-overlay' ) . then ( reo => reo . reportRuntimeError ( error ) ) ;
96
- }
97
- Sentry . captureException ( error ) ;
98
- console . error ( error ) ;
86
+ return safeTakeEvery ( actionPattern . type , fn ) ;
99
87
}
0 commit comments