Introduction to Redux Sagas

Share

Handling asynchronous events in React / Redux can sometimes be quite challenging. Up to some point, we can handle async events manually by bridging different debounce functions and components’ states, which ultimately bloats our components.

So, in order to separate our view and business logic and keep the code clean, we’ll need some code abstraction.

What’s redux-saga?

It’s a helper library which allows us to better organize all the async operations by using ES6 Generators – called “sagas”. Sagas work in a similar way Redux reducers do, meaning that they’ll get triggered by specific actions that they’re watching – esentially, async middleware between some view action and a Redux reducer that’s responsible for handling data and updating store.

How it works?

The principle behind writing sagas mimics the one behind writing Redux reducers. We need to take some time and consider separating our business logic into fragments, each serving a specific purpose (e.g. Auth service). We should do the same with the view logic, implementing clean, simple components that trigger specific actions and listen to store changes.

Visually, we could present the flow like this:

Component action trigger (button click dispatches some view action)
    -> saga watcher (specific saga watcher catches the action type)
    -> async action (do API calls and yield results as a different action type)
    -> reducer (catch yielded action)
    -> store update
    -> render
 The first thing is to create our saga middleware and bind it to the Redux store.
// lib/redux/configStore.js
 
import { createStore, applyMiddleware, compose } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducers from 'redux/reducers';
import sagas from 'redux/sagas';
 
const sagaMiddleware = createSagaMiddleware();

export function configStore (initialState) {
    const composer = compose(applyMiddleware(
        sagaMiddleware
    ));

    const store = createStore(reducers, initialState, composer);
    sagaMiddleware.run(sagas);
    return store;
}

Structurally, sagas consist of one root saga and its child sagas, all of them being exported generator functions. Considering that we’ll probably have more than one business logic fragment, we should import them as separate files / modules, while we keep the root saga in /sagas/index.js and yield all the rest.

// lib/redux/sagas/index.js
 
import { all } from 'redux-saga/effects';
import exampleSaga from './modules/example';
 
export default function* sagas () {
    yield all([
        exampleSaga()
    ]);
}

Our single saga contains a watcher function for a specific view action, which will invoke handleExample function. The takeLatest method means that we’ll only get results from the latest method call, in case there are consecutive / parallel ones.

Yielding success or error events will invoke a specific reducer and the store update will occur.

// lib/redux/sagas/modulex/example
 
function* handleExample () {
    try {
        const response = yield call(Api.get, API_RESOURCE);
 
        yield put({ type: VIEW_ACTION_TYPE_SUCCESS, payload: { data: response } });
    } catch (err) {
        yield put({ type: VIEW_ACTION_TYPE_ERROR, payload: { error: 'error' } });
    }
}
 
function* watchExample () {
    yield takeLatest(SOME_VIEW_ACTION_TYPE, handleExample);
}
 
export default function* exampleSaga () {
    yield all([
        watchExample()
    ]);
}

Sagas expose mulitple methods called “effects”. Some of them we’ve already shown in the code examples above, such as “all” and “takeLatest”. We won’t go in depth regarding those, you can read more about those from the official documentation (https://redux-saga.js.org/).

Conclusion

Redux-saga isn’t the “always use” library. It should be used selectively, depending on the project requirements, team and deadlines.

It is definitely going to help with cleaning out components’ structure, as we can abstract that logic into sagas. The code will be much more scalable, reusable, and writing unit tests for sagas takes very small effort, as all sagas are pure functions, so there’s no side effects.

Share

Sign in to get new blogs and news first:

Leave a Reply

David Lazić

Senior Developer @ Deploy Inc.
mm

David is an experienced developer @Deploy, committed to maintaining cutting edge technical skills and up-to-date industry knowledge. He is focused on problem solving, code architecture and integration of diverse set of technologies and environments. In his free time David enjoys gaming, hiking, calisthenics and the occasional beer.

Sign in to get new blogs and news first.

Categories