saga-toolkit 2.2.4 → 2.2.6
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 +48 -51
- package/dist/index.d.mts +2 -1
- package/dist/index.d.ts +2 -1
- package/package.json +2 -2
- package/src/types.ts +2 -0
package/README.md
CHANGED
|
@@ -48,40 +48,32 @@ yarn add saga-toolkit
|
|
|
48
48
|
|
|
49
49
|
### 1. Create a "Saga Action"
|
|
50
50
|
|
|
51
|
-
Instead of `createAsyncThunk` or standard
|
|
51
|
+
Instead of `createAsyncThunk` or standard action creators, use `createSagaAction`. This creates an Async Thunk that returns a promise which your Saga will resolve or reject.
|
|
52
52
|
|
|
53
|
-
```
|
|
54
|
-
/* slice.
|
|
55
|
-
import { createSlice } from '@reduxjs/toolkit'
|
|
53
|
+
```typescript
|
|
54
|
+
/* slice.ts */
|
|
55
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
|
56
56
|
import { createSagaAction } from 'saga-toolkit'
|
|
57
57
|
|
|
58
|
+
interface User {
|
|
59
|
+
id: string
|
|
60
|
+
name: string
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
const name = 'users'
|
|
59
64
|
|
|
60
|
-
// Define the action
|
|
61
|
-
export const fetchUser = createSagaAction(`${name}/fetchUser`)
|
|
62
|
-
|
|
63
|
-
const initialState = {
|
|
64
|
-
data: null,
|
|
65
|
-
loading: false,
|
|
66
|
-
error: null,
|
|
67
|
-
}
|
|
65
|
+
// Define the action: <Returned, ThunkArg>
|
|
66
|
+
export const fetchUser = createSagaAction<User, string>(`${name}/fetchUser`)
|
|
68
67
|
|
|
69
68
|
const slice = createSlice({
|
|
70
69
|
name,
|
|
71
|
-
initialState,
|
|
70
|
+
initialState: { data: null as User | null, loading: false },
|
|
72
71
|
extraReducers: (builder) => {
|
|
73
72
|
builder
|
|
74
|
-
.addCase(fetchUser.pending, (state) => {
|
|
75
|
-
|
|
76
|
-
state.error = null
|
|
77
|
-
})
|
|
78
|
-
.addCase(fetchUser.fulfilled, (state, { payload }) => {
|
|
73
|
+
.addCase(fetchUser.pending, (state) => { state.loading = true })
|
|
74
|
+
.addCase(fetchUser.fulfilled, (state, action: PayloadAction<User>) => {
|
|
79
75
|
state.loading = false
|
|
80
|
-
state.data = payload
|
|
81
|
-
})
|
|
82
|
-
.addCase(fetchUser.rejected, (state, { error }) => {
|
|
83
|
-
state.loading = false
|
|
84
|
-
state.error = error
|
|
76
|
+
state.data = action.payload
|
|
85
77
|
})
|
|
86
78
|
},
|
|
87
79
|
})
|
|
@@ -91,18 +83,17 @@ export default slice.reducer
|
|
|
91
83
|
|
|
92
84
|
### 2. Connect to a Saga
|
|
93
85
|
|
|
94
|
-
Use `takeEveryAsync` (or `takeLatestAsync`, etc.) to listen for the action.
|
|
86
|
+
Use `takeEveryAsync` (or `takeLatestAsync`, etc.) to listen for the action. Use the `SagaActionFromCreator` helper to type your worker sagas perfectly.
|
|
95
87
|
|
|
96
|
-
```
|
|
97
|
-
/* sagas.
|
|
88
|
+
```typescript
|
|
89
|
+
/* sagas.ts */
|
|
98
90
|
import { call } from 'redux-saga/effects'
|
|
99
|
-
import { takeEveryAsync } from 'saga-toolkit'
|
|
91
|
+
import { takeEveryAsync, SagaActionFromCreator } from 'saga-toolkit'
|
|
100
92
|
import { fetchUser } from './slice'
|
|
101
|
-
import API from './api'
|
|
102
93
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const userId = meta.arg
|
|
94
|
+
// helper for clean typing
|
|
95
|
+
function* fetchUserSaga(action: SagaActionFromCreator<typeof fetchUser>) {
|
|
96
|
+
const userId = action.meta.arg
|
|
106
97
|
|
|
107
98
|
// The return value here resolves the promise!
|
|
108
99
|
const user = yield call(API.getUser, userId)
|
|
@@ -110,27 +101,32 @@ function* fetchUserSaga({ meta }) {
|
|
|
110
101
|
}
|
|
111
102
|
|
|
112
103
|
export default function* rootSaga() {
|
|
113
|
-
yield takeEveryAsync(fetchUser.type, fetchUserSaga)
|
|
104
|
+
yield takeEveryAsync(fetchUser.pending.type, fetchUserSaga)
|
|
114
105
|
}
|
|
115
106
|
```
|
|
116
107
|
|
|
117
108
|
### 3. Dispatch and Await in Component
|
|
118
109
|
|
|
119
|
-
|
|
110
|
+
#### [Pro Tip] Use `bindActionCreators`
|
|
111
|
+
To keep your component code clean and avoid passing `dispatch` everywhere, we recommend using `bindActionCreators`.
|
|
120
112
|
|
|
121
|
-
```
|
|
122
|
-
/* UserComponent.
|
|
113
|
+
```tsx
|
|
114
|
+
/* UserComponent.tsx */
|
|
115
|
+
import { useMemo } from 'react'
|
|
123
116
|
import { useDispatch } from 'react-redux'
|
|
124
|
-
import {
|
|
117
|
+
import { bindActionCreators } from 'redux'
|
|
125
118
|
import { fetchUser } from './slice'
|
|
126
119
|
|
|
127
|
-
const UserComponent = ({ id }) => {
|
|
120
|
+
const UserComponent = ({ id }: { id: string }) => {
|
|
128
121
|
const dispatch = useDispatch()
|
|
122
|
+
|
|
123
|
+
// Bind actions once
|
|
124
|
+
const actions = useMemo(() => bindActionCreators({ fetchUser }, dispatch), [dispatch])
|
|
129
125
|
|
|
130
126
|
const handleFetch = async () => {
|
|
131
127
|
try {
|
|
132
|
-
//
|
|
133
|
-
const user = await
|
|
128
|
+
// Clean awaitable call!
|
|
129
|
+
const user = await actions.fetchUser(id).unwrap()
|
|
134
130
|
console.log('Got user:', user)
|
|
135
131
|
} catch (error) {
|
|
136
132
|
console.error('Failed to fetch:', error)
|
|
@@ -143,27 +139,28 @@ const UserComponent = ({ id }) => {
|
|
|
143
139
|
|
|
144
140
|
## API Reference
|
|
145
141
|
|
|
146
|
-
### `createSagaAction(typePrefix)`
|
|
147
|
-
Creates a Redux Toolkit Async Thunk
|
|
142
|
+
### `createSagaAction<Returned, ThunkArg>(typePrefix)`
|
|
143
|
+
Creates a Redux Toolkit Async Thunk bridge.
|
|
148
144
|
- **Returns**: An enhanced thunk action creator.
|
|
149
145
|
|
|
150
146
|
### `takeEveryAsync(pattern, saga, ...args)`
|
|
151
|
-
Spawns a `saga` on each action
|
|
152
|
-
- Automatically resolves the promise associated with the action
|
|
153
|
-
- Automatically rejects the promise if the saga errors.
|
|
147
|
+
Spawns a `saga` on each action.
|
|
148
|
+
- Automatically resolves/rejects the promise associated with the action.
|
|
154
149
|
|
|
155
150
|
### `takeLatestAsync(pattern, saga, ...args)`
|
|
156
|
-
Same as `takeEveryAsync`, but cancels
|
|
157
|
-
-
|
|
151
|
+
Same as `takeEveryAsync`, but cancels previous running task on new actions.
|
|
152
|
+
- Propagates cancellation to the saga and rejets the promise with "Aborted".
|
|
158
153
|
|
|
159
154
|
### `takeAggregateAsync(pattern, saga, ...args)`
|
|
160
|
-
Wait for the saga to finish
|
|
161
|
-
-
|
|
155
|
+
Wait for the saga to finish. Subsequent identical actions dispatched while it's running will all share the **same promise result**.
|
|
156
|
+
- Perfect for de-duplicating rapid "Refresh" calls.
|
|
162
157
|
|
|
163
158
|
### `putAsync(action)`
|
|
164
|
-
Dispatches an action
|
|
165
|
-
-
|
|
166
|
-
|
|
159
|
+
Dispatches an action and waits for its Saga to finish.
|
|
160
|
+
- `const result = yield putAsync(otherAction())`
|
|
161
|
+
|
|
162
|
+
### `SagaActionFromCreator<typeof actionCreator>`
|
|
163
|
+
TypeScript helper to extract the correct action type for your Saga worker.
|
|
167
164
|
|
|
168
165
|
## License
|
|
169
166
|
|
package/dist/index.d.mts
CHANGED
|
@@ -19,6 +19,7 @@ interface Request {
|
|
|
19
19
|
handled?: boolean;
|
|
20
20
|
}
|
|
21
21
|
type SagaAction<Returned, ThunkArg = void> = AsyncThunk<Returned, ThunkArg, object>;
|
|
22
|
+
type SagaActionFromCreator<T extends (...args: any[]) => any> = ReturnType<ReturnType<T>['pending']>;
|
|
22
23
|
|
|
23
24
|
declare function takeEveryAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]): redux_saga_effects.ForkEffect<never>;
|
|
24
25
|
declare function takeLatestAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]): redux_saga_effects.ForkEffect<Generator<Generator<unknown, Request, unknown> | redux_saga_effects.TakeEffect | redux_saga_effects.ForkEffect<unknown> | redux_saga_effects.CancelEffect, never, {
|
|
@@ -31,4 +32,4 @@ declare function putAsync(action: Action | PutEffect | AsyncThunkAction<unknown,
|
|
|
31
32
|
|
|
32
33
|
declare const createSagaAction: <Returned, ThunkArg = void>(type: string) => SagaAction<Returned, ThunkArg>;
|
|
33
34
|
|
|
34
|
-
export { type Deferred, type Request, type SagaAction, type SagaWorker, createSagaAction, putAsync, takeAggregateAsync, takeEveryAsync, takeLatestAsync };
|
|
35
|
+
export { type Deferred, type Request, type SagaAction, type SagaActionFromCreator, type SagaWorker, createSagaAction, putAsync, takeAggregateAsync, takeEveryAsync, takeLatestAsync };
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface Request {
|
|
|
19
19
|
handled?: boolean;
|
|
20
20
|
}
|
|
21
21
|
type SagaAction<Returned, ThunkArg = void> = AsyncThunk<Returned, ThunkArg, object>;
|
|
22
|
+
type SagaActionFromCreator<T extends (...args: any[]) => any> = ReturnType<ReturnType<T>['pending']>;
|
|
22
23
|
|
|
23
24
|
declare function takeEveryAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]): redux_saga_effects.ForkEffect<never>;
|
|
24
25
|
declare function takeLatestAsync<A extends Action = Action>(pattern: ActionPattern<A> | Channel<A>, saga: (action: A, ...args: unknown[]) => unknown, ...args: unknown[]): redux_saga_effects.ForkEffect<Generator<Generator<unknown, Request, unknown> | redux_saga_effects.TakeEffect | redux_saga_effects.ForkEffect<unknown> | redux_saga_effects.CancelEffect, never, {
|
|
@@ -31,4 +32,4 @@ declare function putAsync(action: Action | PutEffect | AsyncThunkAction<unknown,
|
|
|
31
32
|
|
|
32
33
|
declare const createSagaAction: <Returned, ThunkArg = void>(type: string) => SagaAction<Returned, ThunkArg>;
|
|
33
34
|
|
|
34
|
-
export { type Deferred, type Request, type SagaAction, type SagaWorker, createSagaAction, putAsync, takeAggregateAsync, takeEveryAsync, takeLatestAsync };
|
|
35
|
+
export { type Deferred, type Request, type SagaAction, type SagaActionFromCreator, type SagaWorker, createSagaAction, putAsync, takeAggregateAsync, takeEveryAsync, takeLatestAsync };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "saga-toolkit",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.6",
|
|
4
4
|
"description": "An extension for redux-toolkit that allows sagas to resolve async thunk actions.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"vitest": "^1.0.0"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@reduxjs/toolkit": "^1.9.5",
|
|
61
|
+
"@reduxjs/toolkit": "^1.9.5 || ^2.0.0",
|
|
62
62
|
"redux-saga": "^1.0.0"
|
|
63
63
|
}
|
|
64
64
|
}
|
package/src/types.ts
CHANGED