Example code
import { AnyAction } from 'redux';
interface Props {
data: string;
}
export interface WatcherResult extends AnyAction {
payload: PROPS
}
type TacPopulate = () => WatcherResult;
const acPopulate: TacPopulate = () => ({
type: 'POPULATE',
payload: {}
});
function* sagaWorker(
action: WatcherResult
): Generator<StrictEffect, WatcherResult, Props> {
return = {
data: 'hello world'
});
}
function* sagaWatcher() {
yield takeEvery('POPULATE', sagaWorker);
}
// trigger
dispatch({
type: 'POPULATE'
});
The method correctly dispatches but the reducer does not receive the new payload. It seems I always have to do an additional 'yield put' like this, which seems superfluous code and bloat which I have to receive by separate reducer method.
function* sagaWorker(
action: WatcherResult
): Generator<StrictEffect, WatcherResult, Props> {
yield put({
type: 'YET_ANOTHER_ACTIONTYPE',
payload: {
data: 'hello world'
}
})
}
It sounds like what you are asking is to change the existing action. That's not how redux-saga works. In your example, your reducer will receive two actions:
{type: "POPULATE"}
{type: "YET_ANOTHER_ACTIONTYPE", payload: {data: "hello world"}}
When the saga "takes" an action, that action still goes through to the store.
If you want to stop an action and replace it then you need custom middleware. Here we look for "POPULATE" actions with an empty payload
and replace it with one with data.
import { Middleware } from "redux";
export const replaceAction: Middleware = (store) => (next) => (action) => {
if (action.type === "POPULATE" && !action.payload) {
console.log("caught action");
const data = "hello world";
const replacedAction = {
...action,
payload: { data }
};
return next(replacedAction);
} else return next(action);
};
which I have to receive by separate reducer method.
Maybe, not always. Does your reducer need to do anything at all when you call {type: "POPULATE"}
with no data? Sometimes the "trigger action" is used to set some sort of state like isLoading: true
in the reducer. Other times you can skip over it entirely.
which seems superfluous code and bloat
Using thunks with createAsyncThunk is a lot easier to implement if you aren't committed to saga.
As far as your Typescript types are concerned, the action created by acPopulate
is invalid because the payload
is empty (maybe there was something here in your actual code). You would need to change this up so that there is a difference between a populate request, which has no payload, and a populate success which contains the data.
const requestPopulate = () => ({
type: "POPULATE_REQUEST"
});
const populateWithData = (data: string) => ({
type: "POPULATE_SUCCESS",
payload: {
data
}
});
function* sagaWorker(action: AnyAction) {
yield put(populateWithData("hello world"));
}
function* sagaWatcher() {
yield takeEvery("POPULATE_REQUEST", sagaWorker);
}