Handling Asynchronous Actions with Redux Thunk

Robin Kim
The Startup
Published in
7 min readJun 24, 2019

Redux is a JavaScript library and design pattern that encourages single source of truth by decoupling state from complex component trees. This calls for a new way of making asynchronous actions as the vanilla React way consists of constantly fetching from inside lifecycle methods, such as componentDidMount, to update component states. There are three general ways React developers go about tackling this problem: Redux Promise Middleware, Redux Saga, and Redux Thunk. This short tutorial will explore Redux Thunk and how it handles asynchronous functions.

Why Do We Need the Thunk Middleware?

Redux Thunk is a middleware, meaning that it intercepts every action object before it reaches a reducer. The middleware ensures that the asynchronously-retrieved data will be added to the redux store accordingly. Here are the reasons why we need Redux Thunk as a middleware:

  1. Asynchronous actions are difficult to deal with because we have less control over the order in which actions get dispatched. Adding Redux Thunk allows us to fire everything in the order that we want because it’s able to stop everything until the request or fetch call is resolved.
  2. Reducers must be pure functions. This stops us from making web requests from within reducers.
  3. Actions must return plain JavaScript objects. Without our Redux Thunk middleware, Redux will error out if an action creator returns something other than a JS object (i.e. a Promise, function, or undefined).

With Redux Thunk, actions no longer have to strictly return JavaScript objects! Action creators can return functions and these functions have access to certain parameters, including dispatch and getState. Because of this, action creators can dispatch multiple actions within itself and an action can be calibrated to be dispatched only after an asynchronous call has been resolved. Redux Thunk gives us the ability to fire actions in a specific order and thus, gives us a way of handling asynchronous actions in Redux.

The Setup

This short tutorial will go through the baseline requirements needed to set up Redux and Redux Thunk from scratch. We will make a call to the JSONPlaceholder API to retrieve some posts data, which we will render on our main app container upon load of the page.

Start by creating a new app using create-react-app in your terminal, then navigate into the newly created directory to install redux, react-redux, and redux-thunk. Once these packages are added to your project, go ahead and start up your application with npm start.

npx create-react-app project-redux-thunk
cd project-redux-thunk
npm install --save redux react-redux redux-thunk
npm start

create-react-app starts us out with a basic setup of folders and files. Add two new folders within src and name them components and redux. Move the file App.js into the components folder and create three new files in the redux folder: actionCreators.js, reducer.js, store.js. The folder structure of your app should look something like this at this point:

/public/src
/components
/App.js
/redux
/actionCreators.js
/reducer.js
/store.js
App.css
App.test.js
indes.css
index.js
...

Clean up index.js, edit its import pathways, and add the Provider component to pass down store (which we will create later on in redux/store.js).

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './components/App';
import { Provider } from 'react-redux';
import store from './redux/store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
document.getElementById('root')
);

Clean up components/App.js as well and add the connect method from react-redux so that the App.js file looks like this:

import React, { Component } from 'react';
import { connect } from 'react-redux';
class App extends Component {
render() {
<div className="App"></div>
}
}
export default App;

Now, let’s set up the middleware React Thunk within src/redux/store.js. We need to specify all of the middleware we are using when creating the Redux store. We do this by first using a Redux method called compose to set up an advanced store, which allows us to combine multiple middleware — in our case, Redux Thunk and Redux developer tools. We then use the method createStore, which takes in applyMiddleware within its second argument. src/redux/store.js should look as follows:

import { createStore, applyMiddleware, compose } from 'redux';
import reducer from './reducer';
import thunk from 'redux-thunk';
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk))); export default store;

Next, we’ll edit actionCreators.js. As mentioned before, Redux Thunk allows action creators to return functions (instead of objects) that can dispatch other actions after a fetch call has been resolved. We'll the following lines of code to src/redux/actionCreators.js:

const URL = "https://jsonplaceholder.typicode.com/posts" function fetchedPosts(posts) {
return { type: "FETCHED_POSTS", posts }
}
function loadingPosts() {
return { type: "LOADING_POSTS" }
}
function fetchingPosts(posts) {
return (dispatch) => {
dispatch(loadingPosts())
fetch(URL)
.then(res => res.json())
.then(posts => {
dispatch(fetchedPosts(posts))
})
}
}
export { fetchingPosts };

Here is what’s happening above: we make a fetch call to an endpoint of /posts of a JSONPlaceholder API, which will return some Post objects or dummy data for us. We only need to export the function fetchingPosts at the end because it is the only function we will need to invoke from outside of this file. The rest of the functions declared from within actionCreators.js will be triggered by fetchingPosts. Once fetchingPosts is invoked (i.e. when a user visits the website to view a list of posts), the action created by loadingPosts is dispatched to indicate that we’re in the process of making a request to the API. Even though this process may only take a couple milliseconds, we can use this “loading” state to visualize the process to the user via a spinner or progress bar. Once we receive a response from the fetch call, the second action — created by fetchedPosts — is dispatched, indicating the the fetch process has ended successfully.

And the last of our redux folder — reducer.js.

Import combineReducers from redux so that we can combine multiple reducers (each accounting for a respective piece of state) into a single root reducer that will be exported into store.js to create our Redux store. src/redux/reducer.js:

import { combineReducers } from 'redux'; const postsReducer = (oldState = [], action) => {
switch (action.type) {
case "FETCHED_POSTS":
return action.posts

default:
return oldState
}
}
const loadingReducer = (oldState = "false", action) => {
switch (action.type) {
case "FETCHED_POSTS":
return false
case "LOADING_POSTS":
return true
default:
return oldState
}
}
const rootReducer = combineReducers({
posts: postsReducer,
loading: loadingReducer,
})
export default rootReducer;

As you can see, we have two reducers: postsReducer, which stores our Post objects in an array after the fetch has been made, and loadingReducer, which indicates the state of the fetch call. loadingReducer will come in handy when we want to implement a visual indicator, like a loading spinner or progress bar.

At this point, our application should be error-free and should show a blank page upon load of the browser.

Let’s revisit our App.js file to connect it to our redux portion of the app.

Import fetchingPosts from actionCreators.js and map it to the App component using mapDispatchToProps. Now that we have access to the action creator fetchingPosts within the App.js component, we can call it inside componentDidMount like so:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchingPosts } from '../redux/actionCreators';
class App extends Component {
componentDidMount() {
this.props.fetchingPosts();
}
render() {
<div className="App"></div>
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchingPosts: () => { dispatch(fetchingPosts()) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

Although we have nothing rendering on our page yet, if we go to our Redux developer tools, we will see that actions with types ‘LOADING_POSTS’ and ‘FETCHED_POSTS’ have been dispatched and our state has been updated with an array of Post objects!

Hooray! We’ve successfully fetched our Post objects and have updated our state with them using Redux and Redux Thunk! All we have to do now is get the data to show up on our page. To do that, we need to first introduce mapStateToProps method within App.js so that the App component can read the posts and loading pieces of our state. Then within the render method, we’ll save a collection of <li> elements of post titles in a variable called allPosts. src/components/App.js:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchingPosts } from '../redux/actionCreators';
class App extends Component {
componentDidMount() {
this.props.fetchingPosts();
}
render() {
const allPosts = this.props.posts.map(post => <li key={post.id}>{post.title}</li>)
return this.props.loading ? <div>Loading...</div> : (
<div className="App">
<ul>{allPosts}</ul>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
posts: state.posts,
loading: state.loading
}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchingPosts: () => { dispatch(fetchingPosts()) }
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);

With these final edits to our code in App.js, our app will display the text “Loading…” for a quick moment as it fetches the Post objects from the API. Once our fetch call is resolved, the action created by the action creator fetchedPosts will be dispatched and our state property loading will go back to false. When this.props.loading evaluates to false again, the text “Loading…” will be replaced with an unordered list of 100 posts like so:

What our final product should look like!

You can find the complete project directory for this tutorial here!

Conclusion

This tutorial quickly walked through a Redux Thunk implementation, including the organization and set up of the Redux store, reducers, and action creators. To recap: Redux allows us to decouple state from the components in our React application. While the Redux design pattern makes it easier to manage state in large, complex apps, we need a new way of making asynchronous calls. Redux Thunk offers a solution as it gives us more control over the order in which actions are dispatched within asynchronous processes.

--

--