How to use async methods inside Redux actions using Redux-Thunk

Redux-Thunk Flow

If you are using Redux, probably you noticed that actions only can return plain objects, something like:

{ type: HERE_THE_ACTION_TYPE, anObject }

If we try to return any other thing like for example a function, the browser will nicely remember it with a message:

Action must be plain object

In the message, Redux is telling us the solution: Use custom middleware for async actions

This article will help us to prevent the error using Redux-Thunk.

First steps

We are going to do an app from scratch so first steps are related to setup the app and add the required packages.

npx create-react-app demo-react-thunk
cd demo-react-thunk
yarn add redux react-redux redux-thunk

The good stuff

Now we are going to create a component that will list some users. Since we want to show how to use Redux-Thunk we will need:

  • An action: it will fetch the user data
  • A reducer: to put the data in the global state
  • An store: to save the global state
  • A component: to show the data

The action

Lets create the file src/actions/user.js with the following content:

export const fetchUsers = () => {
return dispatch => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(json => dispatch({ type: 'FETCH_ALL_USERS', json }))
}
}

What are we doing there?

Instead of returning a plain object we are returning a function that expects the dispatch method as parameter, the function is asking for some dummy data using https://jsonplaceholder.typicode.com/ service, transform the response to json and then return the plain object action using dispatch. For details about dispatch method, please read the official redux docs

The reducer

This is a common reducer, I mean, we don't need to change anything to support async actions. Lets create the file src/reducers/user.js with the following content:

import { combineReducers } from 'redux';
const user = (state = { users: [] }, action) => {
switch (action.type) {
case 'FETCH_ALL_USERS':
return {
...state,
users: action.json
};
default:
return state;
}
};
export default combineReducers({
user
});

The store

Here is where magic happens, in the file src/index.js we should import the following:

import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers/user';

and then we should create the store with following code:

const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware
)
);

What we are doing here, is asking our store to apply a middleware, remember the original message about using custom middleware for async actions?. The middleware in this case is Redux-Thunk that will help us to run async methods in the action.

The code for src/index.js should looks like the following

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { Provider } from 'react-redux';
import { applyMiddleware, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers/user';
const store = createStore(
rootReducer,
applyMiddleware(
thunkMiddleware
)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);

The component

Finally, we need a component to show the users fetched by redux action, we are going to usesrc/App.js to show the data, it will looks like the following code:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import { connect } from "react-redux";
import { fetchUsers } from "./actions/user"
class App extends Component {
componentDidMount() {
this.props.fetchUsers();
}
render() {
const { users } = this.props;
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">Redux Thunk Demo</h1>
</header>
<section>
{
users.length > 0 &&
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
</tr>
</thead>
<tbody>
{
users.map(user =>
<tr key={user.id}>
<td>{user.name}</td>
<td>{user.email}</td>
<td>{user.phone}</td>
</tr>
)
}
</tbody>
</table>
}
</section>
</div>
);
}
};
const mapStateToProps = (state) => {
return state.user;
}
const mapDispatchToProps = (dispatch) => ({
fetchUsers: () => dispatch(fetchUsers())
});
export default connect(mapStateToProps, mapDispatchToProps)(App);

That's all folks

Now we can run the app and see how it shows some user data using async actions in redux.



If you like this story clap as many times as you want, and to see similar stories about technology, check our publications and leave us a comment if you have any question.

If you need a team that can help you to implement your ideas in React JS or React Native, feel free to ping us using our website and filling the contact form.

Gustavo
Mail: gustavo@alturasoluciones.com
Twitter: @Aguardientico
LinkedIn: www.linkedin.com/in/gustavoagonzalez
Blog in spanish: http://aguardientech.blogspot.com

Like what you read? Give Gustavo Gonzalez a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.