From Flux to Redux: Async Actions the easy way

The Flux architecture is based on some great ideas (check out our visual cheatsheet if you want to know more), but we found a big flaw when implementing it through Facebook’s own Dispatcher implementation. This brought us to Redux, a flux-inspired library that received high praise from the Flux creator herself.

Redux is very young and really tiny at 2kB, but already incredibly stable and feature rich. It is also a very non-opinionated library, and the just completed (almost) docs are very keen to point out how you can do things in many different ways. This is great if you already have your strong preferences, but doesn’t help the newcomer.

One thing some of us found particularly hard to digest was the documentation for Async Actions, so I wrote a simplified version to use with my team. You can find it below; it is based on the Todo List example in the “Basics” section of the docs: if you didn’t read that already, you should do it now.

Async Actions the easy way

As opposed to Flux, In Redux action creators must be pure functions with no side effects. This means that they can’t directly execute any async code, as the Flux architecture instead suggests, because async code has side effects by definition.

Fortunately, Redux comes with a powerful middleware system, and one specific middleware library, redux-thunk, that allows us to integrate async calls into our action creators in a clean and easy way.

First of all, we need to integrate the redux-thunk middleware into our store. We can do this in the Todo List example by changing a few lines inside index.js; from this:

import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/App';
import todoApp from './reducers';

let store = createStore(todoApp);

To this:

import React from 'react';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import App from './containers/App';
import todoApp from './reducers';

let store =  applyMiddleware(thunk)(createStore)(todoApp);

Before this change, store.dispatch could only accept simple Action objects. With redux-thunk, dispatch can also accept a thunk function.

  • If dispatch receives an Action object, redux-thunk will do nothing, and the reducers will get called with the action as usual, and change the state synchronously.
  • If instead dispatch receives a thunk function, redux-thunk will execute it. It will be the thunk function’s responsibility to actually dispatch an action object. More importantly, the thunk function doesn’t need to be pure, so it can contain async calls, and can dispatch some (synchronous) action only after the async call has finished, also using the data received from the call. Let’s see this with a simple example for the Todo app.

In a real app, we’ll want to keep our Todo list synchronized with our server. For now we’ll only focus on the addTodo action creator; the one from the Basics section will only update the state in the client. Instead, we want it to:

  • Optimistically update the local state and UI
  • Send the data to the server, so it can update the DB
  • Manage the server response

To achieve this, we still need our “optimistic” addTodo action creator, but we also need an async thunk action creator to handle the whole async process. To keep the changes to the code at a minimum, we’ll rename the optimistic action creator, but not the action type that is sent to the reducers (and used in the switch statement to act). This code goes into actions.js:

// renamed optimistic action creator - this won't be called directly 
// by the React components anymore, but from our async thunk function
export function addTodoOptimistic(text) {
  return { type: ADD_TODO, text };
}

// the async action creator uses the name of the old action creator, so 
// it will get called by the existing code when a new todo item should 
//  be added
export function addTodo(text) {
  // we return a thunk function, not an action object!
  // the thunk function needs to dispatch some actions to change 
  // the Store status, so it receives the "dispatch" function as its
  // >first parameter

  return function(dispatch) {
    // here starts the code that actually gets executed when the 
    //  addTodo action creator is dispatched

    // first of all, let's do the optimistic UI update - we need to 
    // dispatch the old synchronous action object, using the renamed 
    // action creator
    dispatch(addTodoOptimistic(text));

    // now that the Store has been notified of the new todo item, we 
    // should also notify our server - we'll use here ES6 fetch 
    // function to post the data
    fetch('/add_todo', {
      method: 'post',
      body: JSON.stringify({
        text
      })
    }).then(response => {
      // you should probably get a real id for your new todo item here, 
      // and update your store, but we'll leave that to you
    }).catch(err => {
    // Error: handle it the way you like, undoing the optimistic update,
    //  showing a "out of sync" message, etc.
    });
  // what you return here gets returned by the dispatch function that 
  // used this action creator
  return null; 
  }
}

The above code and its comments show how you can do async calls, mixing them with optimistic updates, while still using the usual syntax: dispatch(actionCreator(...)). The action creator itself is still a pure function, but the thunk function it returns doesn’t need to be, and it can do our async calls for us. If needed, the thunk can also access the current store state, as it receives getState as its second argument: return function(dispatch, getState) { ...

That’s all! If you want to learn more about Redux, you can find the full docs here: http://rackt.github.io/redux/

  • http://www.mbedded.ninja/ gbmhunter

    Thanks for the tips. Pointing out that you can access the current state from a thunk with getState() helped me greatly!

  • A. Luca B

    Hello Dan

    thanks for the nice tutorial.

    I’ve a question that i would like to discuss with someone prepared on Redux.

    I am very perplexed by how async is managed in the framework: i feel that the thunk concept violates the concept of uniform state machine that is the core of the framework. A thunk function played in the middleware is no more historicizable and the sequence of the action request is no more deterministic. What happens if the user gives another input or for some reason an action is dispatched deterministically during the async flow ? The sequence of the actions becomes completely dependent from the asynchronous steps.

    I think the asynchronous actions should be totally external actors with respect to the redux architecture.

    I hope that i was clear in my question and i would be very happy to hear what do you think

    • Sean May

      So far as I’m aware, Redux is entirely deterministic in terms of updates regarding state based on actions dispatched, but is entirely non-deterministic in terms of what order actions are dispatched in (ie: allowing the user or network or timers, et cetera, to fire whichever actions are deemed necessary, in whichever order is deemed necessary).

      There is no real difference between that and an action creator which dispatches an action into the store (which is synchronously resolved) when a request is started, and then dispatches another action into the store (which is synchronously resolved) when the request is complete.

      And I suppose that depends on whether you perceive the action creators as a core piece of Redux, or as a convention which is followed when working with Redux.

      The way I see it, the store and its helpers are the core of the deterministic aspect, and its subscription method is then the system which concurrently brings all interested parties back together.

      Whether the asynchronous action happens in the UI, in the network, in a timer, or elsewhere, its resultant action(s) culminate in determinism from the point of entry into the store, which is all you can really ask for (short of asking users to always click and type the same values in the same sequence).

  • Sida Zhou

    Very helpful article

  • http://www.paintballpr.net Luis Betancourt

    Wow thank you so much for this article. I finally got the redux-thunk working. Im still a little confused as to why it works.
    The second options is how the docs teach you http://redux.js.org/docs/api/applyMiddleware.html but for some reason it won’t work for me. The 1st option works perfect. Anyone knows why?

    // article
    (1) const store = applyMiddleware(thunk)(createStore)(reducer);

    //docs
    (2) const store = createStore(reducer, applyMiddleware(thunk))

  • gskema

    So basically redux is just the executor for our action creators, because store expects a sync function (for immediate action object). Couldn’t we technically import store to action creators and make functions that dispatch the actions themselves? Then we could write async code inside the functions as well. Or does thunk middle impose some kind of order/queue to actions?

  • brownieboy

    > In Redux action creators must be pure functions

    Not so. In Redux, reducers must be pure. Action creators don’t have to be.