Folder Structure and Scripts

Let me take the action in this blog as an example.
// app.action.ts export const UPDATE_BLOG = "UPDATE_BLOG"; export type UpdateBlog = ReturnType<typeof updateBlog>; export const updateBlog = (update: Partial<TAppState["blog"]>) => { return { type: UPDATE_BLOG, payload: update, }; };
We can do our api-related logic in the .saga
below! Later whenever we want to modify our api, saga is the only place we need to look at:
// app.saga.ts import { takeEvery, fork } from 'redux-saga/effects'; import * as appActions from "../actions/app.actions"; function* watchUpdateBlog() { yield takeEvery(appActions.UPDATE_BLOG, updateBlog); } function* updateBlog(action: appActions.UpdateBlog) { console.log("I am saga"); yield put(your action, if any); } export default [ fork(watchUpdateBlog) ]
// root.saga.ts import { all } from "redux-saga/effects"; import appSaga from "./app.saga"; function* rootSaga() { yield all([...appSaga]); } export default rootSaga;
// app.reducer.ts import * as appActions from "../actions/app.actions"; export default (state: TAppState = initialState, action: AnyAction): TAppState => { switch (action.type) { case appActions.UPDATE_BLOG: { const update = (action as appActions.UpdateBlog).payload; return { ...state, blog: { ...state.blog, ...update } } default: return state; } }
// root.reducer.ts import { combineReducers } from "redux"; import appReudcer from "./app.reducer"; const rootReducer = combineReducers < any > { app: appReudcer, }; export default rootReducer;
Finally:
// store.ts import { createStore, applyMiddleware } from "redux"; import { createLogger } from "redux-logger"; import createSagaMiddleware from "redux-saga"; import rootReducer from "../reducers/root.reducer"; import rootSaga from "../sagas/root.saga"; const logger = createLogger({ collapsed: true }); const sagaMiddleware = createSagaMiddleware(); const store = createStore(rootReducer, applyMiddleware(logger, sagaMiddleware)); sagaMiddleware.run(rootSaga); export default store;
Now
import { Provider } from "react-redux"; ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("root") );
and happy saga!

Intercept the Async Actions and Error Handling
import { put, call } from "redux-saga/effects"; export const safe = (showLoading: boolean, saga: any, ...args: any) => function* (action: any) { try { if (showLoading) { // yield put(loadingActions.setLoading(true)); } console.log("intercepted"); yield call(saga, ...args, action); } catch (error) { switch (error.request.status) { case 400: console.log(`${error.response.status}: Bad Request`); break; case 401: break; case 404: console.log(`${error.response.status}: Not Found`); break; case 405: case 500: console.log(`${error.response.status}: Internal Server Error`); break; default: console.log(`${error.response.status}: Service Unavailable`); break; } const response = error.response.data; // yield put(showAlert([response.message], 'error')); } finally { if (showLoading) { // yield delay(500); // yield put(loadingActions.setLoading(false)); } } };
In app.saga.ts
now we can replace our controller updateBlog
by
// app.saga.ts import { safe } from "./middlewares/safe"; function* watchUpdateBlog() { // if true, do whatever you want, like displaying loading popover yield takeEvery(appActions.UPDATE_BLOG, safe(true, updateBlog); }
Result:
