Ohans Emmanuel Author, Understanding Redux. I Love God. I Love GF a little too much 💕🤣 http://thereduxjsbooks.com

Data fetching in Redux apps — a 100% correct approach

11 min read 3096

Redux is a great tool that solves one of the main problems of UI frameworks: state management.

State management on the client side can quickly grow into a nightmare, and the unidirectional flow of data Redux enforces makes it easy to understand how events alter the state of your application.


Sadly, state management is just one of the many issues you have to deal with while building robust applications. What about handling side effects (like network requests, the most common)?

Redux, by itself, doesn’t provide a solution out of the box. Fortunately, the community has a good number of libraries maintained to solve the problem.

redux-thunk? redux-saga? redux-observable? redux-promise?

Now, just which of these is right for your project?

The truth is that each of these solutions were built with different approaches, use cases, and mental models in mind.

They all have their pros and cons.

I don’t intend to discuss all of the possible approaches, but let’s have a look at some of the most common patterns with a simple application.

A fake Medium, huh ?

Here’s the small application I’ll be walking you through. Github Repo

Take a look at the application screenshot above. It’s arguably very simple. There’s a bunch of text and a medium clap icon to the left. You may grab the Github repo for this app.

Note that the Medium clap is clickable. Here’s how I built the medium clap clone in case that fascinates you.

The app with a medium clap clone

Even for this simple application, you have to fetch data from the server. The JSON payload required for displaying the required view may look like this:

  "numberOfRecommends": 1900,
  "title": "My First Fake Medium Post",
  "subtitle": "and why it makes no intelligible sense",
  "paragraphs": [
      "text": "This is supposed to be an intelligible post about something intelligible."
      "text": "Uh, sorry there’s nothing here."
      "text": "It’s just a fake post."
      "text": "Love it?"
      "text": "I bet you do!"

The structure of the app is indeed simple, with 2 major components: Article and Clap

In components/Article.js :

The article component is a stateless functional component that takes in title, subtitle, and paragraphs props. The rendered component looks like this:

const Article = ({ title, subtitle, paragraphs }) => {
  return (
      {paragraphs.map(paragraph => <p>{paragraph.text}</p>)}

Where StyledArticle is a regular div element styled via the CSS-in-JS solution, styled-components .

It doesn’t matter if you’re familiar with any CSS in JS solutions. StyledArticle could be replaced with a div styled via good ol’ CSS.

Let’s get that over with and not begin an argument 😂

In components/Clap.js :

The medium clap component is exported within this directory. The code is slightly more involved and beyond the scope of this article. However, you can read up on how I built the medium clap — it’s a 5 minute read.

With both Clap and Article components in place, the App component just composes both components as seen in containers/App.js

class App extends Component {
  state = {};
  render() {
    return (
          <Clap />
          <Article />

Again, you could replace StyledApp with a regular div and style it via CSS.

Now, to the meat of this article.

Considering the various alternative solutions to fetching data.

Let’s have a look at some of the different ways you could chose to fetch data in your Redux app, and also consider their pros and cons.

The most popular options are arguably, redux-thunk and redux-saga.


Redux Thunk and Redux-Promise

One of the most important things to remember is that every third-party library has its learning curve and potential scalability issues.

Of all the community libraries for managing side effects in Redux, those that work like Redux-thunk and Redux-promise are the easiest to get started with.

The premise is simple.

For redux-thunk, you write an action creator that doesn’t “create” an object but returns a function. This function gets passed the getState and dispatch functions from Redux.

Let’s have a look at how the fake medium app may utilise the redux-thunk library.

First, install the redux-thunk library:

yarn add redux-thunk

To make the library work as expected, it has to be applied as middleware.

In store/index.js

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
const store = createStore(rootReducer, applyMiddleware(thunk));

The first line in the code block above imports the createStore and applyMiddleware functions from redux.

Line 2 imports the thunk from redux-thunk .

Line 3 creates the store but with an applied middleware.

Now, we are ready to make an actual network request.

I’ll be making use of axios library for making network requests but be free to replace that with any http client of your choice.

Initiating a network request is actually pretty easy with redux-thunk. You create an action creator like this (i.e an action creator that returns a function):

export function fetchArticleDetails() {
  return function(dispatch) {
    return axios.get("https://api.myjson.com/bins/19dtxc")
      .then(({ data }) => {

Upon mounting the App component, you dispatch this action creator:

componentDidMount() {

And that’s it. Be sure to check the full code diff, as I am only highlighting the key lines here:

The article details have been successfully fetched from the server.

With that, the details of the article has been fetched and displayed in the app.

What exactly is wrong with this approach?

If you’re building a very small application, redux-thunk solves the problem and it’s perhaps the easiest to get along with.

However, the ease of use does come at a cost. Let’s consider three drawbacks.

1. Every action creator will have repetitive functionality for handling errors and setting headers.

Here’s the action creator we wrote earlier:

export function fetchArticleDetails() {
   return function(dispatch) {
     return axios.get("https://api.myjson.com/bins/19dtxc")
      .then(({ data }) => {

In most applications, you’ll need to make multiple requests and in different methods, GET, POST etc.

Assume you had another action creator called recommendArticle. Now that may also look like this:

export function recommendArticle (id,  amountOfRecommends) {
  return  function (dispatch) {
    return axios.post("https://api.myjson.com/bins/19dtxc, {

Oh, and if you wanted to fetch a user’s profile ?

export function fetchUserProfile() {
    return function(dispatch) {
      return axios.get("https://api.myjson.com/bins/19dtxc")
       .then(({ data }) => {

It doesn’t take long to see that there’s a lot of repeated functionality. And if you wanted to catch errors, you’d add a catch block to every action creator ?

2. With more async action creators, testing gets harder.

Async stuff is generally harder to test. Not impossible or difficult to test, it just makes it considerably harder to test.

Keeping action creators as stateless as possible, and making them simple functions makes them easier to debug and test.

With more action creators you include in your application, testing gets harder.

3. Changing the server communication strategy gets even harder.

What if a new senior developer came around and decided the team had to move from axios to another http client, say, superagent . Now, you’d have to go change it the different (multiple) action creators.

Not so easy, is it?

Redux Saga and Redux-Observable

These are slightly more complicated than redux-thunk or redux-promise.

redux-saga and redux-observable definitely scale better, but they require a learning curve. Concepts like sagas and RxJS have to be learned, and depending on how much experience the engineers working on the team have, this may be a challenge.

So, if redux-thunk and redux-promise are too simple for your project, and redux-saga and redux-observable will introduce a layer of complexity you want to abstract from your team, where do you turn?

Custom middleware!

Most solutions like redux-thunk, redux-promise and redux-saga use a middleware under the hood anyway. Why can’t you create yours?

Did you just say “why reinvent the wheel?”

Is this the perfect solution?

While reinventing the wheel does sound outrightly like a bad thing, give it a chance.

A lot of companies already build custom solutions to fit their needs anyway. In fact, that’s how a lot of open source projects began.

So, what would you expect from this custom solution ?

  1. A centralised solution i.e in one module.
  2. Can handle various http methods, GET, POST, DELETE and PUT
  3. Can handle setting custom headers
  4. Supports custom error handling e.g. to be sent to some external logging service, or for handling authorisation errors.
  5. Allows for onSuccess and onFailure callbacks
  6. Supports labels for handling loading states

Again, depending on your specific needs, you may have a larger list.

Now, let me walk you through a decent starting point. One you can adapt for your specific use case.

A redux middleware always begins like this:

const apiMiddleware = ({dispatch}) => next => action => {
  next (action)

And, here’s the full-fledged code for the custom api middleware. It may look like a lot at first, but I’ll explain every line shortly.

Here you go:

With barely 100 lines of code, which you can grab from GitHub, you have a customised solution with a flow that is easy to reason about.

I promised to explain each line, so first, here’s an overview of how the middleware works:

The general flow of the custom middleware

First, you make some important imports, and you’ll get to see the usage of those very soon.

  1. Set up the middleware

This is the typical setup required for a redux middleware. i.e

const apiMiddleware = ({ dispatch }) => next => action => {}

2. Dismiss irrelevant action types

if (action.type !== API) return;

The condition above is important to prevent any action except those of type, API from triggering a network request.

3. Extract important variables from the action payload

const {
  } = action.payload;

In order to make a successful request, there’s the need to extract the following from the action payload.

url represents the endpoint to be hit, method refers to the HTTP method of the request, data refers to any data to be sent to the server or query parameter, in the case of a GET or DELETE request, onSuccess and onFailure represent any action creators you’ll like to dispatch upon successful or failed request, and label refers to a string representation of the request.

You’ll see these used in a practical example shortly.

4. Handle any HTTP method

const dataOrParams = ["GET", "DELETE"].includes(method) ? "params" : "data";

Because this solution uses axios, and I think most HTTP clients work like this anyway, GET and DELETE methods use params while other methods may require sending some data to the server.

Thus, the variable, dataOrParams will hold any of the values, params or data depending on the method of the request.

If you have some experience developing on the web, this should not be strange.

5. Handle Globals

// axios default configs
  axios.defaults.baseURL = process.env.REACT_APP_BASE_URL || "";
  axios.defaults.headers.common["Authorization"] = `Bearer${token}`;

Most decent applications will have some authorisation layer, a baseUrl and some default headers. Technically, every api client will very likely have some defaults for every request.

This is done by setting some properties on the axios object. I’m doubtless the same can be done for any client of your choosing.

6. Handle loading states

if (label) {

A label is just a string to identify a certain network request action. Just like an action’s type.

If a label exists, the middleware will dispatch an apiStart action creator.

Here’s what the apiStart action creator looks like:

export const apiStart = label => ({
  type: API_START,
  payload: label

The action type is API_START.

Now, within your reducers you can handle this action type, to know when a request begins. I’ll show an example shortly.

Also, upon a successful or failed network request, an API_END action will also be dispatched. This is perfect for handling loading states since you know exactly when the request begins and ends.

Again, I’ll show an example shortly.

7. Make the actual network request, handle errors, and invoke callbacks

      url: `${BASE_URL}${url}`,
      [dataOrParams]: data
    .then(({ data }) => {
    .catch(error => {
     if (error.response && error.response.status === 403) {
    .finally(() => { if (label) { dispatch(apiEnd(label)); } });

It isn’t as complex as it looks.

axios.request is responsible for making the network request, with an object configuration passed in. These are the variables you extracted from the action payload earlier.

Upon a successful request, as seen in the then block, dispatch an apiEnd action creator.

That looks like this:

export const apiEnd = label => ({
  type: API_END,
  payload: label

Within your reducer, you can listen for this and kill off any loading states as the request has ended.

After that is done, dispatch the onSuccess callback.

The onSuccess callback returns whatever action you’d love to dispatch after the network request is successful. There’s almost always a case for dispatching an action after a successful network request e.g to save the fetched data to the redux store.

If an error occurs, as denoted within the catch block, also fire off the apiEnd action creator, dispatching an apiError action creator with the failed error:

export const apiError = error => ({
  type: API_ERROR,

You may have another middleware that listens for this action type and makes sure it the error hits your external logging service.

You dispatch an onFailure callback as well. Just incase you need to show some visual feedback to the user. This also works for toast notifications.

Finally, I have shown an example of handling an authentication error:

if (error.response && error.response.status === 403) {

In this example, I dispatch an accessDenied action creator which takes in the location the user was on.

I can then handle this accessDenied action in another middleware.

You really don’t have to handle these in another middleware. They can be done within the same code block, however, for careful abstraction, it may make more sense for your project to have these concerns separated.

And that’s it!

The custom middleware in action

I’ll now refactor the fake medium application to use this custom middleware. The only changes to be made is to include this middleware:

import apiMiddleware from "../middleware/api";
const store = createStore(rootReducer, applyMiddleware(apiMiddleware));

And then edit the fetchArticleDetails action to return a plain object.

export function fetchArticleDetails() {
  return {
    type: API,
    payload: {
      url: "https://api.myjson.com/bins/19dtxc",
      method: "GET",
      data: null,
      onSuccess: setArticleDetails,
      onFailure: () => {
        console.log("Error occured loading articles");

function setArticleDetails(data) {
  return {
    payload: data

Note how the payload from fetchArticleDetails contains all the needed information required by the middleware.

There’s a little problem though.

Once you go beyond one action creator, it becomes a pain to write the payload object every single time. Especially when some of the values are null or have some default values.

For ease, you may abstract the creation of the action object to a new action creator called, apiAction

function apiAction({
  url = "",
  method = "GET",
  data = null,
  onSuccess = () => {},
  onFailure = () => {},
  label = ""
}) {
  return {
    type: API,
    payload: {

Using ES6 default parameters, note how apiAction has some sensible defaults already set.

Now, in fetchArticleDetails you can do this:

function fetchArticleDetails() {
  return apiAction({
    url: "https://api.myjson.com/bins/19dtxc",
    onSuccess: setArticleDetails,
    onFailure:() => {console.log("Error occured loading articles")},

This could even be simpler with some ES6:

const fetchArticleDetails = () => apiAction({
   url: "https://api.myjson.com/bins/19dtxc",
   onSuccess: setArticleDetails,
   onFailure: () => {console.log("Error occured loading articles")},

A lot simpler!

And the result is the same, a working application!

a working application 🙂

To see how labels can be useful for loading states, I’ll go ahead and handle the API_START and API_END action types within the reducer.

  if (action.payload === FETCH_ARTICLE_DETAILS) {
     return {
        isLoadingData: true

case API_END:
   if (action.payload === FETCH_ARTICLE_DETAILS) {
      return {
        isLoadingData: false

Now, I’m setting a isLoadingData flag in the state object based on both action types, API_START and API_END

Based on that I can set up a loading state within the App component.

Here’s the result:

See the “loading ….” text ?

That worked!

Remember, the custom middleware I’ve shared here is only to serve as a good starting point for your application. Evaluate to be sure this is right for your exact situations. You may need a few tweaks depending on your specific use case.

For what it’s worth, I have used this as a starting point on fairly large projects without regretting the decision.


I definitely encourage you to try out the various available options for making network requests in a redux app before committing to any.

Sadly, it becomes difficult to refactor after choosing a strategy for a grown application.

In the end, it’s your team, your application, your time, and ultimately, you alone can make the choice for yourself.

Do not forget to check out the code repository on Github, and thanks to https://leanpub.com/redux-book from which this article was inspired.

Catch you later!

Plug: LogRocket, a DVR for web apps


LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Try it for free.

Ohans Emmanuel Author, Understanding Redux. I Love God. I Love GF a little too much 💕🤣 http://thereduxjsbooks.com

8 Replies to “Data fetching in Redux apps — a 100% correct…”

  1. What if you have multiple fetches like fetchDetails, fetchArticles, etc?
    How do you stop the loading prop from going true/false/true/false?

  2. Thanks, great article, so what happens when you fire two requests and they both update loadingData to true one after the other, yet one completes first and seemingly loading is complete for both requests since loadingData would have been set to false by the first resolving promise?

  3. Going with the custom middleware approach, I’ve found a way to further improve code readability and decoupling.

    If you’re interested, take a look at react-redux-api-tools on npm or on github (labcodes/react-redux-api-tools).
    I’d love to have your input!

    Thanks <3

  4. Redux working fine without combine reducers. But once reducer(s) are cobmined it does not work. No data is dispatched to UI etc.

  5. hi There , so i am building an enterprise application and i really like this article where to come up with our custom middlewear but will be really nice if the auther answer the above questions.
    i am using Redux , redux-thunk and axios.and i want to externalize the api and dont want to repeat myself.

  6. You actually lost me with this statement. It’s not a problem of redux-thunk it’s a problem of having a wrong abstraction that is not a valid point for avoiding redux-thunk

    What if a new senior developer came around and decided the team had to move from axios to another http client, say, superagent . Now, you’d have to go change it the different (multiple) action creators.

Leave a Reply