Asynchronous Actions

Learn how the Redux data flow works with asynchronous data and how to use Redux middleware for asynchronous logic.

All actions discussed previously were executed synchronously. This means that each action creator was executed whenever we wanted to modify the state without waiting for the result of an asynchronous process to finish. In many dynamic web applications, this situation is highly unlikely. Many React applications have to deal with asynchronous data flow (in particular, network requests)… Synchronous action creators don’t offer a great solution to this problem. The dispatch method of a store expects an action that contains a simple object containing a type property.

Redux middleware

Redux middleware concepts, and in particular Redux Thunk middleware, can help with this problem.

The createStore() function from the redux package can deal with up to three parameters:

  1. The reducer function: This is the only mandatory parameter and deals with the executed actions of our state by returning a state for each action dispatched.
  2. Initial state: We can pre-populate the store with data by providing a value in the initial state. This initial state is also passed to the reducer function.
  3. An enhancer function: This function can be used to enhance the store’s capability with our own functionality. In this case, we enhance it with the middleware mentioned above.

If the createStore function receives another function as a second parameter, the second parameter will be treated as an enhancer function. If the second parameter takes the form of anything different, the second parameter will be treated as the initial state and passed to the reducer function.

Redux middleware is wrapped around the dispatch method and interrupts the usual call before it is executed. It can modify the action before it is sent to the reducer and returns a new dispatch function. If we want to pass asynchronous functions (for example, Promises) to the dispatch() method, we can use the store enhancer to register the middleware that allows us to do just that. The most common piece of middleware is the thunk middleware.

Installation

Install thunk middleware via:

npm install redux-thunk

Or, here’s how to install it using Yarn:

yarn add redux-thunk

Registering the middleware

Once the thunk middleware has been installed, it has to be registered via the Redux applyMiddleware() function in the enhancer. We import the middleware and import the applyMiddleware() function from the redux package. The middleware we want to use has to be passed to the applyMiddleware() function as a parameter. In this case, we are passing thunk:

import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';

// ...

const store = createStore(reducer, applyMiddleware(thunk));

Creating action creators for asynchronous code execution

By implementing this piece of thunk middleware, we can now easily compose action creators that execute asynchronous code and only dispatch the actions once we have obtained a result. The thunk function is an action creator that returns a function whose parameters are also a dispatch() and a getState() function. We can decide ourselves when we should dispatch an action in the thunk action creator function:

const delayedAdd = (newTodo) => {
  return (dispatch, getState) => {
    setTimeout(() => {
      return dispatch({
        type: 'ADD_TODO',
        payload: newTodo,
      });
    }, 500);
  };
};

store.dispatch(
  delayedAdd({
    id: 1,
    text: 'Explaining thunk actions',
    done: false,
  })
);

In this example, we have created a delayedAdd action creator. It receives a new todo element and then returns a new function in the form of (dispatch, getState) => {}. The thunk middleware ensures that this function always receives the dispatch() and getState() functions. After a delay of 500ms, we call the dispatch() function with the ADD_TODO action and add the new object.

Dispatching action using asynchronous action creator

To be able to dispatch said action, we can use the asynchronous action creator in the same fashion as the synchronous action creator - by passing the called function to the dispatch() function to the store: store.dispatch(ActionCreator). The thunk middleware will recognize if a thunk function is being used, execute it and pass it dispatch and getState as an argument.

If you’re familiar with the arrow function syntax of ES2015, you’ll see how we can simplify things further:

const delayedAdd = (newTodo) => (dispatch, getState) => {
  setTimeout(() => {
    return dispatch({
      type: 'ADD_TODO',
      payload: newTodo,
    });
  }, 500);
};

In this example, we use a shorter arrow function with an implicit return, which can then be directly returned by the function called by the thunk middleware. By not having to write another return, we eliminate another two lines of code. However, keep in mind that we don’t trade readability and understanding for shorter code.

Get hands-on with 1200+ tech skills courses.