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:
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.
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.
Note that the Medium clap is clickable. Here’s how I built the Medium clap clone in case that interests you.
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
.
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
.
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.
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.
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?
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:
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.
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?”
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?
GET
, POST
, DELETE
, and PUT
onSuccess
and onFailure
callbacksAgain, 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:
First, you make some important imports, and you’ll get to see the usage of those very soon.
This is the typical setup required for a Redux API middleware:
const apiMiddleware = ({ dispatch }) => next => action => {}
There, step one is done. Easy!
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.
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 hitmethod
: the HTTP method of the requestdata
any data to be sent to the server or query parameter in the case of a GET
or DELETE
requestonSuccess
and onFailure
: represent any action creators you’ll like to dispatch upon successful or failed requestlabel
: a string representation of the requestYou’ll see these used in a practical example shortly.
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.
// 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.
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.
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!
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!
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:
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.
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!
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
27 Replies to "Data fetching with Redux and Axios"
What if you have multiple fetches like fetchDetails, fetchArticles, etc?
How do you stop the loading prop from going true/false/true/false?
Have you gotten a solution for this?
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?
That isn’t really something this is concerned with, thats a state management or application management concern, not the api middleware responsibility.
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
How is this different from a collection of thunks?
Redux working fine without combine reducers. But once reducer(s) are cobmined it does not work. No data is dispatched to UI etc.
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.
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.
You can use Promise.all to set it only once true false
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?
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?
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.
Thanks Todd Skelton!!!!! The tutorial should be changed with your solution! It’s great!
isLoadingData should be within it’s own domain in the redux store. Like this:
{
details: {
isLoadingData: false,
},
articles: {
isLoadingData: false,
}
}
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.
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?
`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.
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,
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?
You can use dotenv with webpack’s DefinePlugin to pass some environment variables to your react application. With this scaffolding in place, you can create different environment files for different environments and load the appropriate configuration for use. This post on SO explains this: https://stackoverflow.com/a/42182661/399435
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.
so in your case there are many components subscribed to one state, isn’t it a bad practice?
I prefer new redux toolkit createAsyncAction which returns promise and have cancelation funcunalty, you cant await mutlipe actions with this api middlware.
see https://github.com/klis87/redux-requests, this can be solved by counters instead of booleans
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?
It helped to solve a real website problem with loading state in React v18. Many thanks!