Ohans Emmanuel Visit me at www.ohansemmanuel.com to learn more about what I do!

Data fetching with Redux and Axios

13 min read 3709

Using Redux Apps And Axios For Data Fetching

Editor’s note: This post has been updated on 26 August 2022 to update and improve information about data fetching with Redux and Axios, as well as to mention an additional simple option for fetching data using React Hooks.

As many developers know, state management is one of the many issues you have to deal with while building robust applications. It can quickly grow into a nightmare, especially on the client side.

Redux enforces a unidirectional flow of data, which makes it easy to understand how events alter the state of your application. Great! But what about handling side effects, such as network requests, the most common side effect?

Let’s explore some of the solutions Redux offers for fetching and updating data as well as how to set up a custom middleware solution to address your specific needs and any side effects you may encounter.

In this article:

What is Redux?

Redux is a state container and great tool that solves one of the main problems of UI frameworks: state management. With Redux, application states can be maintained predictably by events called actions.

The predictable quality of Redux state management is that if the actions are replayed, we will arrive at the correct data state every time.

There are a number of libraries that extend the capabilities of Redux in state management for our applications. But how can we tell which of these is right for our project?

The truth is that each of these solutions were built with different approaches, use cases, and mental models in mind, so they all have their pros and cons. In this blog, I won’t discuss all of the possible approaches, but let’s have a look at some of the most common patterns with a simple application.

To explore the different options for client application state management, we’ll use a simple React application.



A simple example of fetching data from the server

Let’s use a fake Medium post as an example in our simple React application.

Take a look at the application screenshot below. Wouldn’t you agree that it’s very simple? All it contains is a bunch of text and a Medium clap icon to the left. You can grab the GitHub repo for this app to follow along.

Screenshot Of Simple Example Application Containing Medium Clap Icon (Left) And Dummy Blog Text (Right)

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

User Cursor Shown Clicking Green Medium Clap Icon To Left Of Dummy Blog Text Before Gif Fades To Black And Loops

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 two major components: Article and Clap.

Example Data Fetching App Structure Shown With Clap Labeled And Boxed In Red While Article Is Labeled And Boxed In Yellow

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 (
    <StyledArticle>
      <h1>{title}</h1>
      <h4>{subtitle}</h4>
      {paragraphs.map(paragraph => <p>{paragraph.text}</p>)}
    </StyledArticle>
  );
};

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


More great articles from LogRocket:


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.

The Medium clap component is exported within components/Clap.js. 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 five-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 (
      <StyledApp>
        <aside>
          <Clap />
        </aside>
        <main>
          <Article />
        </main>
      </StyledApp>
    );
  }
}

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

Now, to the meat of this article.

Various solutions for fetching and updating data

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

Using React state hooks

React provides Hooks, which act as shorthand access to React features such as state. In this section, we will look specifically at the React State Hook.

Using Hooks, we can build a component that has access to features like states without writing a class to extend React.Component and referencing this.state

To use the State Hook, we import useState from the React library, like so:

import {useState} from 'react';

Simply put, useState provides a shorthand way to create a state data object and a function for updating it.

useState accepts a value — any value type, such as integer, string, boolean, object, etc. — and returns an array of two items. The first item is the variable, which holds the value. The second is the function for updating the variable.

Let’s see a small example using our Clap component written as a function and not a class:

// src/components/Clap.js

import {useState} from 'react'

function generateRandomNumber(min, max) {
    return Math.floor(Math.random() * (max - min + 1) + min);
}

function Clap() {
  const [isClicked, setIsClicked] = useState(false)
  const [count, setCount] = useState(0)
  const [countTotal, setCountTotal] = useState(generateRandomNumber(500, 1000))

  const handleClick = () => {
     // set is clicked to true - this makes our button green
     setIsClicked(true)
     setCount(count + 1)
     setCountTotal(countTotal + 1)
  }

  return (
    <div>
      <button id="clap" className="clap" onClick={handleClick}>
        <span>
          {/*<!--  SVG Created by Luis Durazo from the Noun Project  -->*/}
          <svg
            id="clap--icon"
            xmlns="http://www.w3.org/2000/svg"
            viewBox="-549 338 100.1 125"
            className={`${isClicked && "checked"}`}
          >
            <path d="M-471.2 366.8c1.2 1.1 1.9 2.6 2.3 4.1.4-.3.8-.5 1.2-.7 1-1.9.7-4.3-1-5.9-2-1.9-5.2-1.9-7.2.1l-.2.2c1.8.1 3.6.9 4.9 2.2zm-28.8 14c.4.9.7 1.9.8 3.1l16.5-16.9c.6-.6 1.4-1.1 2.1-1.5 1-1.9.7-4.4-.9-6-2-1.9-5.2-1.9-7.2.1l-15.5 15.9c2.3 2.2 3.1 3 4.2 5.3zm-38.9 39.7c-.1-8.9 3.2-17.2 9.4-23.6l18.6-19c.7-2 .5-4.1-.1-5.3-.8-1.8-1.3-2.3-3.6-4.5l-20.9 21.4c-10.6 10.8-11.2 27.6-2.3 39.3-.6-2.6-1-5.4-1.1-8.3z" />
            <path d="M-527.2 399.1l20.9-21.4c2.2 2.2 2.7 2.6 3.5 4.5.8 1.8 1 5.4-1.6 8l-11.8 12.2c-.5.5-.4 1.2 0 1.7.5.5 1.2.5 1.7 0l34-35c1.9-2 5.2-2.1 7.2-.1 2 1.9 2 5.2.1 7.2l-24.7 25.3c-.5.5-.4 1.2 0 1.7.5.5 1.2.5 1.7 0l28.5-29.3c2-2 5.2-2 7.1-.1 2 1.9 2 5.1.1 7.1l-28.5 29.3c-.5.5-.4 1.2 0 1.7.5.5 1.2.4 1.7 0l24.7-25.3c1.9-2 5.1-2.1 7.1-.1 2 1.9 2 5.2.1 7.2l-24.7 25.3c-.5.5-.4 1.2 0 1.7.5.5 1.2.5 1.7 0l14.6-15c2-2 5.2-2 7.2-.1 2 2 2.1 5.2.1 7.2l-27.6 28.4c-11.6 11.9-30.6 12.2-42.5.6-12-11.7-12.2-30.8-.6-42.7m18.1-48.4l-.7 4.9-2.2-4.4m7.6.9l-3.7 3.4 1.2-4.8m5.5 4.7l-4.8 1.6 3.1-3.9" />
          </svg>
        </span>
        <span id="clap--count" className="clap--count">
          +{count}
        </span>
        <span id="clap--count-total" className="clap--count-total">
          {countTotal}
        </span>
      </button>
    </div>
  );
}

As we can see in our example above, we initialized all our data — isClicked, count and countTotal — using the State Hook. Also, on our handleClick event, we updated our state data using the appropriate functions.

Now let’s introduce another React Hook called useEffect. This Hook can be used together with the useState Hook to load state data fetched from requests. Here’s an example:

import {useState, useEffect} from 'react'
import axios from 'axios'

function Example() {
  const [post, updatePost] = useState({title: ''})

  useEffect(() => {
     axios.get("https://api.myjson.com/bins/19dtxc")
        .then(({ data }) => {
            updatePost(data)
        })
  })

  return (
    <div>
       <p>{post.title}</p>
       ...
    </div>
  )
}

Using React Hooks for API calls works fine when our data is self-contained and influenced by a single component. When we have data shared by several components, we can no longer rely on Hooks.

Now let’s explore some Redux libraries for more complex data changes. The most popular options are arguably redux-thunk and redux-saga.

Ready?

Using redux-thunk and redux-promise

An important thing to remember is that every third-party library has its learning curve and potential scalability issues. But 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. A thunk is a piece of code that does some delayed work or, simply put, is the logic used to update a state later.

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 utilize 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. The second line imports the thunk from redux-thunk. The third line creates the store, but with an applied middleware.

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

We’ll use the Axios library to make network requests, but feel free to replace it with another HTTP client of your choice.

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

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

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

componentDidMount() {
    this.props.fetchArticleDetails();
 }

And that’s it. Be sure to check the full code diff, as I am only highlighting the key lines here. With that, the article details have been fetched and displayed in the app, like so:

Data Fetching Api Results Using Redux-Thunk Library

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.

To improve our use of redux-thunk, we can create a class or object for an HTTP request that implements our client of choice. That way, it would be easy to swap out libraries and create an instance that reduces repetition of the base URL and headers, as well as handling the basic network errors.

A good idea is to keep our action creators as stateless as possible. Making them simple functions makes them easier to debug and test.

Using redux-saga and redux-observable

These Redux libraries 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 RxJS and Redux Saga 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 custom middleware the perfect solution?

While reinventing the wheel does initially sound 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?

  • A centralized solution; i.e., in one module
  • Can handle various HTTP methods, including GET, POST, DELETE, and PUT
  • Can handle setting custom headers
  • Supports custom error handling; e.g., to be sent to some external logging service, or for handling authorization errors
  • Allows for onSuccess and onFailure callbacks
  • 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 API middleware always begins like this:

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

You can find the full-fledged code for the custom Redux API middleware on Github. It may look like a lot at first, but I’ll explain every line shortly.

Here you go:

import axios from "axios";
import { API } from "../actions/types";
import { accessDenied, apiError, apiStart, apiEnd } from "../actions/api";


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

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

  const {
    url,
    method,
    data,
    accessToken,
    onSuccess,
    onFailure,
    label,
    headers
  } = action.payload;

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


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


  if (label) {
    dispatch(apiStart(label));
  }

  axios
    .request({
      url,
      method,
      headers,
      [dataOrParams]: data
    })
    .then(({ data }) => {
      dispatch(onSuccess(data));
    })
    .catch(error => {
      dispatch(apiError(error));
      dispatch(onFailure(error));

      if (error.response && error.response.status === 403) {
        dispatch(accessDenied(window.location.pathname));
      }
    })
   .finally(() => {
      if (label) {
        dispatch(apiEnd(label));
      }
   });
};

export default apiMiddleware;

With barely 100 lines of code (which, again, you can grab from GitHub), you have a customized Redux API middleware 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:

Infographic Showing Overview Of How Middleware Works With Six Steps Of Data Fetching Redux Apps Api

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

Set up the middleware

This is the typical setup required for a Redux API middleware:

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

There, step one is done. Easy!

Dismiss irrelevant action types

Our next step is to dismiss irrelevant action types. See the code below:

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

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

Extract important variables from the action payload

In order to make a successful request, there’s the need to extract some important variables from the action payload:

const {
    url,
    method,
    data,
    onSuccess,
    onFailure,
    label,
  } = action.payload;

Here is what each variable represents or refers to:

  • url: the endpoint to be hit
  • method: the HTTP method of the request
  • data 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
  • label: a string representation of the request

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

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 — the GET and DELETE methods use params, while other methods may require sending some data to the server.

Thus, the variable dataOrParams will hold any relevant 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.

Handle globals

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

Most decent applications will have some authorization 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 sure the same can be done for any client of your choosing.

Handle loading states

if (label) {
    dispatch(apiStart(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.

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

axios
    .request({
      url: `${BASE_URL}${url}`,
      method,
      headers,
      [dataOrParams]: data
    })
    .then(({ data }) => {
      dispatch(onSuccess(data));
    })
    .catch(error => {
      dispatch(apiError(error));
      dispatch(onFailure(error));

     if (error.response && error.response.status === 403) {
        dispatch(accessDenied(window.location.pathname));
      }
    })
    .finally(() => { if (label) { dispatch(apiEnd(label)); } });

The code above 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 the following:

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,
  error
});

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

You dispatch an onFailure callback as well, just in case 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) {
     dispatch(accessDenied(window.location.pathname));
  }

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 are to include this middleware, then edit the fetchArticleDetails action to return a plain object.

Including the middleware:

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

Editing the fetchArticleDetails action:

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");
      },
      label: FETCH_ARTICLE_DETAILS
    }
 };
}

function setArticleDetails(data) {
  return {
    type: SET_ARTICLE_DETAILS,
    payload: data
  };
}

Note how the payload from fetchArticleDetails contains all the 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. This can be especially frustrating 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: {
      url,
      method,
      data,
      onSuccess,
      onFailure,
      label
    }
  };
}

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")},
    label: FETCH_ARTICLE_DETAILS
  });
}

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")},
   label: FETCH_ARTICLE_DETAILS
});

A lot simpler — and the result is the same, a working application!

Result Of Data Fetching Application Api With Much Simpler Es6 Approach

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.

case API_START:
  if (action.payload === FETCH_ARTICLE_DETAILS) {
     return {
        ...state,
        isLoadingData: true
     };
}

case API_END:
   if (action.payload === FETCH_ARTICLE_DETAILS) {
      return {
        ...state,
        isLoadingData: false
      };
 }

Now, I’m setting an 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:

Result Of Medium Api Start With Isloadingdata Flag Showing Loading State Before Dummy Blog Text Appears And Gif Fades To Black

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 situation. 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.

Conclusion

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 LeanPub’s Redux Book, which inspired this article.

Catch you later!

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution 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 and mobile apps.

.
Ohans Emmanuel Visit me at www.ohansemmanuel.com to learn more about what I do!

26 Replies to “Data fetching with Redux and Axios”

  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.

  7. How can i do that?

    If i do

    useEffect(() => {
    Promise.all([
    dispatch(fetchDetails()),
    dispatch(fetchArticles()),
    ])

    it’s the same thing, because the flow is the same.
    Any idea?

  8. No. I tried

    useEffect(() => {
    Promise.all([
    dispatch(fetchDetails()),
    dispatch(fetchArticles()),
    ])

    but it’s the same thing, because the flow is the same.
    Any other idea?

  9. Instead of a boolean for is loading data, I use an array and just add each action type that is loading to it and delete each action that is done.

    If the action count is greater than zero, I display the loading. If I only want to display loading for certain types, I can just check the array for that action type.

  10. Thanks Todd Skelton!!!!! The tutorial should be changed with your solution! It’s great!

  11. isLoadingData should be within it’s own domain in the redux store. Like this:
    {
    details: {
    isLoadingData: false,
    },
    articles: {
    isLoadingData: false,
    }
    }

  12. There’s no reason why you can’t just use a wrapper around fetch()/axios(), control state locally and/or globally, depending on what other parts of the application need to be affected by the call. Using redux-middleware means that you are tied to using redux. It also means that you are changing store state when changing the status of fetching/success/failure, it means that you have to go all around the redux cycle to get the data you want, as opposed to handling it locally in the react component. It also will trigger all your mapStateToProps functions – pretty unnecessary if you only need the result of that data local to the component.

  13. What if you need to make 2 calls, one depending on the other?
    For example fetch a users list and if the user is not in the list add it.

    Ho do you do that with this middleware?

  14. `if (action.type !== API) return;`
    You shouldn’t dismiss irrelevant action types here since there are other actions that may depend on other middleware. By returning, you effectively said “game over” for every other action.
    Instead, you should return `next`.

    Either way, I personally think this approach borderlines with an anti-pattern. And it gets complicated quickly!
    Redux’s concern is storing global state, not getting it. Especially since having a service consuming dispatch directly is so trivial compared to this approach. Also, Redux is sync by design. Introducing an async flow there? The amount of code demonstrated above IMHO speaks for itself.

  15. Hello Ohans Emmanuel,
    Allow me to first give you a round of applause and a huge thanks, your middleware is quite perfect, very beautiful coding. But yet I would to like to draw an little attention regarding combinedReducer that this structure works very well only if you have single reducer but what about multiple reducer can help me in this area
    Thank You again,

  16. This is really good.

    What I’m after, and maybe I missed it, are two APIs. In production mode, I use an API to make the real http calls, In dev mode, I serve up pre canned data without making any http calls.
    I haven’t quite worked out how to make this kind of switch, based on the env variables – any ideas?

  17. Why are we even using redux middleware to make api call, why cant we just make the api call in the container and then dispatch the action just to populate the data ??

    I dont see any advantages of using middleware.

  18. so in your case there are many components subscribed to one state, isn’t it a bad practice?

  19. I prefer new redux toolkit createAsyncAction which returns promise and have cancelation funcunalty, you cant await mutlipe actions with this api middlware.

  20. Great article…
    I seem to be getting CORS errors – “Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://api.myjson.com/bins/19dtxc. (Reason: CORS request did not succeed).”

    I’ve tried adding “Access-Control-Allow” headers – but with no luck.

    Any thoughts, please?

Leave a Reply