React-Redux Async example with Thunk middleware .

Ron Lavit
9 min readApr 6, 2018

--

Click Here to see a live example of what we’ll be building.

While learning Redux, one of the most challenging tasks for me was making Async call to external API. When I first started to learn Async Redux I couldn’t find any simple enough example of React-Redux App with API call. The async actions tutorial contained in the documentation is undoubtedly a scalable approach to handle these kinds of situations, but it’s not very simple, and back then it was really over the top for me.

So, I’ve decided to create this kind of tutorial. I’ve built a real life React-Redux application with Async call to API.I’ve tried to keep an application and code as simple as possible to make it easy to understand.

To inspect dispatched actions and Redux state go to the console panel of Chrome. In my App I call news API from https://newsapi.org . You can easily get your own API key from there in case you need one.

Application description:

The application shows top 10 news from selected channel.

-There are six buttons, each of them represents one channel

-Press any channel-button to select a channel .

-Press button “Get top news” to see top news from the selected channel.

-You can select another channel and get top news from there .

The App has this appearance before we press “Get top news” button to fetch news from BBC channel.

I use Redux Thunk middleware in the App to make Async call. The main goal of my article is to show how to deal with asynchronous calls in Redux so I don’t want to get into a lot of details about everything else, just general explanation.

From my experience of learning React+Redux I would strongly advice you to keep file structure well organized and don’t put everything into one file.

Let’s have a quick look at the file structure of my example. Here I use quite popular pattern of dividing all components into two categories: Container and Presentational components. So, we have containers folder and components folder accordingly. Container components know about Redux while Presentational components know nothing about Redux, as simple as that.

src/
components/
App.js
ChannelsField.js
NewsItem.js
containers/
Channel.js
Button.js
TopNews.js
actions/
index.js
reducers/
index.js
css/
styles.css
index.html
index.js

If you don’t want to spend time on a configuration of development environment with Webpack you can clone my repository

The repository contains all necessary packages for this tutorial.

Now a few sentences explaining why making Async call in Redux is not a straightforward task. When we call an asynchronous API, there are three crucial states in time: the state before we start the call, the state between the moment a call start and the moment an answer received, and the state after we receive the answer.

STATE(before call) →STATE(during call) →STATE(answer received)

Each of these three states usually will be obtained by dispatching normal actions that will be processed by reducers synchronously. Generally for any API request you’ll want to dispatch at least two different kinds of actions:

An action informing the reducers that the request began.

The reducers may handle this action by toggling a Loading flag in the state. This way the UI knows it's time to show a spinner or in my App I just show “Loading…” .

An action informing the reducers that the request finished successfully.

The reducers may handle this action by merging the new data into the state they manage and resetting Loading. The UI would hide the spinner, and display the fetched data.

Without middleware, Redux store only supports synchronous data flow. Thus, without any middleware, our action creator function must return plain object only , but with Thunk Middleware we can write action creator functions that returns functions. If Redux Thunk middleware is enabled, any time you attempt to dispatch a function instead of an action object, the middleware will call that function with dispatch method itself as the first argument.

So probably I am repeating the same idea by using different words but I do it intentionally because understanding of Thunk middleware is a quite challenging task and hopefully one of explanations will do its job .

Redux Thunk is middleware for Redux. It basically allows us to return function instead of objects as an action.

Let’s start building the application step by step.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ReactReduxAsyncApp</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div id="root"></div>
</div>
</body>
</html>

I use Bootstrap library for a styling ,so I’ve included a link to this library in the head section. There is no need to include tag <script> with javascript bundle file into index.html because HtmlWebpackPlagin will do this job for us.

index.js is a file where we have to initialize the Redux store .To include the Redux Thunk middleware in the dispatch mechanism we use the applyMiddleware() store enhancer from Redux, as shown below:

index.js

import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import thunk from 'redux-thunk'
import { logger } from 'redux-logger'
import reducer from './reducers'
import './css/styles.css'
import App from "./components/App"
const store = createStore(
reducer,
applyMiddleware(thunk, logger)
)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

Here I use redux-logger tool to inspect in console panel triggered actions and state of Redux store.

components/App.js

This component will render all our application.

import React from 'react'
import ChannelsField from './ChannelsField'
import Button from '../containers/Button'
import TopNews from '../containers/TopNews'
const App = () => (
<div>
< ChannelsField />
<Button />
<TopNews />
</div>
)
export default App;

I would recommend you when there are no other components ready yet to keep this component code as simple as:

import React from ‘react’const App = () => (
<div>
"Hello React"
</div>
)
export default App;

So if you have set up you development environment in the correct way after running git command npm start you will see in you browser ‘Hello React’. When next component is ready you can add this component into App.js and test it in the browser and so on.

containers/Channel.js

This component represents channel button .On click <Channel> will change color and dispatch action getChannel . Action getChannel will set channel property in the Redux state ,later we will use value from channel to fetch top news from selected channel.

import React from 'react'
import { connect } from 'react-redux'
import { getChannel, activateChannel } from '../actions'
let Channel = ({ channelName, channelString, onClick, active }) => (
<div onClick={onClick} className=" col-lg-2 col-md-4 col-sm-6 ">
<div className="channel-button"
style={{ backgroundColor: active === channelString ? 'orange' : ''
}}>
<p>{channelName}</p>
</div>
</div>
)
const mapStateToProps = (state) => ({active: state.channel})const mapDispatchToProps = (dispatch, ownProps) => ({
onClick: () => {
dispatch(getChannel(ownProps.channelString));
}
})
Channel = connect(mapStateToProps,mapDispatchToProps)(Channel)
export default Channel;

In function mapStateToProps of this component I map state.channel to props active of the component.And in mapDispatchToProps I map getChannel -action creator to onClick props .Thus, for example, after click on button ‘BBC’ our Redax state is an object {channel: “bbc-news”} .String from state.channel we use in a call to API .

On this development step, action creator getChannel have to be ready and reducer must know how to deal with action.type -SELECT_CHANNEL.

actions/index.js

export const SELECT_CHANNEL = 'SELECT_CHANNEL';
export const REQUEST_POSTS = 'REQUEST_POSTS';
export const RECEIVE_POSTS = 'RECEIVE_POSTS';
const MY_API_KEY = 'c39a26d9c12f48dba2a5c00e35684ecc';export const getChannel = channel => ({
type: SELECT_CHANNEL,
channel,
});
export const requestPosts = () => ({
type: REQUEST_POSTS,
});
export const receivedPosts = json => ({
type: RECEIVE_POSTS,
json: json.articles,
});
export function fetchPosts(channel) {
return function (dispatch) {
dispatch(requestPosts());
return fetch(`https://newsapi.org/v1/articles?
source=${channel}&apiKey=${MY_API_KEY}`)
.then(
response => response.json(),
error => console.log('An error occurred.', error),
)
.then((json) => {
dispatch(receivedPosts(json));
},
);
};
}

reducers/index.js

import { SELECT_CHANNEL, REQUEST_POSTS, RECEIVE_POSTS } from '../actions';const reducer = (state = {}, action) => {
switch (action.type) {
case SELECT_CHANNEL:
return { ...state, channel: action.channel };
case REQUEST_POSTS:
return { ...state, loading: true };
case RECEIVE_POSTS:
return { ...state, json: action.json, loading: false };
default:
return state;
}
};
export default reducer;

containers/Button.js

Job of the <Button> component is to fetch top channels from API news .In this component we will dispatch an action creator fetchPosts which returns a function and not a plain javascript object.

import React from 'react'
import { connect } from 'react-redux'
import { fetchPosts } from '../actions'
let Button = ({ getPosts, channel }) => (<button
onClick={() => { getPosts(channel) }}
className='btn btn-primary btn-lg btn-block' >
Get top news
</button>
);
const mapStateToProps = (state) => ({ channel: state.channel })const mapDispatchToProps = { getPosts: fetchPosts }Button = connect(mapStateToProps,mapDispatchToProps)(Button)export default Button;

Function map mapStateToProps will map state.channel to channel props of the component . Function mapDispatchToProps will map action creator fetchPosts to props getPosts of the component . Props getPosts receives selected channel string as parameter.

On this development step, all action creator have to be finished and reducer must know how to deal with each of them .

Let’s have closer look on our async action creator fetchPosts, undoubtedly the most complicated one .By the way without Thunk middleware we wouldn’t be able to dispatch this kind of action creator functions.

export function fetchPosts(channel) {
return function (dispatch) {
dispatch(requestPosts());
return fetch(`https://newsapi.org/v1/articles?
source=${channel}&apiKey=${MY_API_KEY}`)
.then(
response => response.json(),
error => console.log(‘An error occurred.’, error),
)
.then((json) => {
dispatch(receivedPosts(json));
},
);
};
}

In action creator fetchPosts first we dispatch requestPosts — regular action creator function. After this action has been dispatched our application will change state to “Loading…”.And then when a response from API will have arrived we dispatch second action creator receivedPosts with json from news API as parameter . On this stage our App will change state from ‘Loading… to display top news from API .

containers/TopNews.js

In this component we will show either ‘Loading…’ or top news, depending on state of the App.

import ReactDOM from 'react-dom'
import React from 'react'
import { connect } from 'react-redux'
import NewsItem from '../components/NewsItem’;
let TopNews = ({ channels, loading }) => {
let topNews = '';
if(channels){
topNews = channels.map((article, index) =>(
<div key={`${index}`} className='row'>
<NewsItem article={channels[index]} />
</div>
)
)
}
if(loading){
topNews = <h3 className="loading-indicator">Loading ...</h3>
}
return (
<div>
{topNews}
</div>
)
}
const mapStateToProps = (state) => ({
channels: state.json,
loading: state.loading
})
TopNews = connect(mapStateToProps,null)(TopNews)export default TopNews;

In mapStateToProps function we map two properties of state: state.json and state.loading as props channels and loading of the component .

During loading time state is an object:

{channel: “bbc-news”, loading: true}.

And when a response has arrived state is an object:

{channel: “bbc-news”, loading: false, json: Array(10)}

Here json is an array of ten objects .Each of objects represent one news with author, title, image and so on . You can inspect json object in the browser console by yourself.

Generally that’s it. My presentational components are quite straightforward and I’ll just paste its code here without any explanation .

components/ChannelsField.js

import React from 'react'
import Channel from '../containers/Channel'
const ChannelsField = () => (
<div className="row" >
<Channel channelName="BBC" channelString="bbc-news" />
<Channel channelName="CNBC" channelString="cnbc" />
<Channel channelName="CNN" channelString="cnn" />
<Channel channelName="FT" channelString="financial-times" />
<Channel channelName="ESPN" channelString="espn" />
<Channel channelName="GOOGLE" channelString="google-news" />
</div>
);
export default ChannelsField;

components/NewsItem.js

import React from 'react';const NewsItem = ({ article }) => (
<article >
<div className="article-wrapper">
<h3 className="text-center">{article.title}</h3>
<img src={article.urlToImage} alt="" />
<p className="text-center">{article.description}</p>
<a href={article.url} target="_blank"> read more </a>
</div>
</article>
);
export default NewsItem ;

Folder css contains a file styles.css where I’ve added some styles for my App to make it a little bit attractive.

You can clone the full App code from my repository:

USAGE
git clone https://github.com/Lavitr/ReduxAsyncActions.git
cd ReduxAsyncActions
npm install
npm start
open http://localhost:8080 (should start automatically )
## Available Commands- `npm start` - start the dev server
- `npm run dev` - create a developer build in `dist` folder
- `npm run build` - create a production ready build in `dist` folder

Please ‘clap’ if you find this article useful.

Thanks for reading!

--

--

Ron Lavit

Frontend | React developer. Currently based in Poland.