image credit: Jean Wimmerlan

Redux-Thunk vs. Redux-Saga

Redux Redux-Saga

By now you've already finished some Redux tutorials and felt like you were ready to test the waters by writing some Redux code of your own.

After some blood, sweat, and tears, you built up a reducer and some actions. You're making some progress! Things are starting to click and you feel like you're finally getting it! Time to hook it up to React.

But then... THUD! You hit the wall: You realize you can't show your component yet because you don't have any data for it!!!

You: Well… okay, Redux, how do I load data?
Redux: Don't care.
You: Got anything to add, React?
React: Not my problem.

Unfortunately for you, ¯\_(ツ)_/¯ is not an answer.

So you read some more blogs… hit up some community resources… but ended up with more questions than answers. As far as you can tell, half the community uses thunks and the other half uses sagas. Well, who is right?

How are thunks different from sagas?

Thunks versus Sagas

In terms of implementation, Redux-Thunk and Redux-Saga couldn't possibly be more different. However, in terms of what you can do with them… well, they're actually very very similar. But not the same. Redux-Thunk and Redux-Saga differ in a few important ways.

But, first, let's cover some background.

Redux-thunk and Redux-saga are both middleware libraries for Redux. Redux middleware is code that intercepts actions coming into the store via the dispatch() method.

An action can be literally anything.

But if you're following best practices, an action is a plain javascript object with a type field, and optional payload, meta, and error fields. E.g.

const loginRequest = {    type: 'LOGIN_REQUEST',    payload: {        name: 'admin',        password: '123',    },};

(by the way, this structure is called Flux Standard Action)

This is what the type signature looks like in TypeScript

type Action = {    type: string;    payload?: any;    meta?: any;    error?: boolean;};


In addition to dispatching standard actions, Redux-Thunk middleware allows you to dispatch special functions, called thunks.

Thunks (in Redux) generally have the following structure:

export const thunkName =   parameters =>        (dispatch, getState) => {            // Your application logic goes here        };

That is, a thunk is a function that (optionally) takes some parameters and returns another function. The inner function takes a dispatch function and a getState function -- both of which will be supplied by the Redux-Thunk middleware.

Here we have an example thunk that attempts to call a login API with a username and password. The thunk first dispatches an action indicating that the request is starting. It then makes the call. Finally, it dispatches either a success action or a failure action depending on whether or not the API call succeeded.

import * as api from 'api';import { loginRequest, loginSuccess, loginFailure } from './loginActions';export const loginThunk =    (name: string, password: string) =>        (dispatch: Function) => {            dispatch(loginRequest());            try {                api.login(name, password);            }            catch (err) {                dispatch(loginFailure(err));            }            dispatch(loginSuccess());        };

When you dispatch your thunk, e.g. dispatch(loginThunk('admin', 'secret')); Redux-Thunk calls your inner function, which is essentially:

(dispatch: Function) => {    dispatch(loginRequest());    try {        api.login('admin', 'secret'); // values from closure    }    catch (err) {        dispatch(loginFailure(err));    }    dispatch(loginSuccess());};

Now that we've covered the basics of Redux-thunk, let's look at Redux-Saga


Redux-Saga middleware allows you to express complex application logic as pure functions called sagas. Pure functions are desirable from a testing standpoint because they are predictable and repeatable, which makes them relatively easy to test.

Sagas are implemented through special functions called generator functions. These are a new feature of ES6 JavaScript. Basically, execution jumps in and out of a generator everywhere you see a yield statement. Think of a yield statement as causing the generator to pause and return the yielded value. Later on, the caller can resume the generator at the statement following the yield.

A generator function is one defined like this. Notice the asterisk after the function keyword.

function* mySaga() {    // ...}

We can rewrite the login functionality from before as a saga. It looks like this:

import * as api from 'api';import { LoginRequestAction, loginSuccess, loginFailure } from './loginActions';function* loginSaga() {    const action: LoginRequestAction = yield take('LOGIN_REQUEST');    const { name, password } = action.payload;    try {        yield call(api.login, name, password);    }    catch (err) {        yield put(loginFailure(err));        return;    }    yield put(loginSuccess());}

Once the login saga is registered with Redux-Saga, it will begin executing immediately. But then the yield take on the the first line will pause the saga until an action with type 'LOGIN_REQUEST' is dispatched to the store. Once that happens, execution will continue.

The Biggest Difference

You might think the biggest difference is in the syntax. Although it's true that writing and reasoning about thunks and sagas are quite different, there's something bigger.

Thunks can never act in response to an action. Redux-Saga, on the other hand, subscribes to the store and can trigger a saga to run or continue when a certain action is dispatched.

Level up Your React + Redux + TypeScript

for free with articles, tutorials, sample code, and Q&A.