From Development

How to handle Async Operations with Redux

It comes as no surprise to anyone that JavaScript is everywhere.
Here at Imaginary Cloud, the most part of the projects we are involved in, use it as the main language. My last project was a fairly large application that involved the usual culprits of the JavaScript ecosystem. We used a React front-end and a Node.js back-end stack.

Why do we use Redux?

When we needed to define a stack for this project, due to its nature of involving a lot of visual presentation and 3D modeling, React presented itself as a no brainer choice, regarding the front-end technology.

Unfortunately, at that time, a clear strategy for state management wasn’t defined. As the project evolved, it started becoming an issue, so the team decided to migrate all the logic related to state management to a Class that presented itself as a Singleton. That would help with all the state-related storage and events. This solution was fine on the short-term, but eventually, it would outgrow his usefulness and a plan was set in motion to find a better alternative. It came in the form of Redux, helped by the introduction of Redux Toolkit, previously known as Redux Starter Kit.

What is a Redux Store?

Redux is a state container for JavaScript applications that lets you circumvent the natural React unidirectional data flow. This is possible as it has a source of truth that you can consult in your entire application, without having to drill downstate as a prop to other components. However, it also allows you to alter using predefined actions, while also keeping a history of such actions and mutations, that can be consulted while testing.

Who created Redux?

When Facebook presented React to the world, it was already evident for them how props could become a major hurdle, when reaching a certain level of complexity. To mitigate this issue, they introduced a concept alongside React called Flux, which describes how a store should work in conjunction with React. Redux, as we know today, stems from just a proof of concept hacked by Dan Abramov, while tinkering with Flux principles for React Europe.

Where is Redux used?

Redux is used in large scale applications where an interaction with one component might propagate changes to the entire page. To facilitate this kind of communication between components, instead of having to create callbacks at the top level of the application, you can just consult the store. In my current project, Redux makes sense, because the application had escalated to a point where props related to its state were being passed down through several layers of components, which in turn was making the code hard to read and even harder to debug.

Dispatching Async Actions using Redux

Redux looked like a godsend when we started migrating old code to this new feature. It made the code look slicker, it was extremely intuitive to understand and reduced drastically the number of props floating around the application. But it was not perfect. The very nature of reducers poses a problem when you’re encompassing the fetching of information in them, and that’s something the Redux community has been tackling for a very long time.

How to handle Async Actions in Redux

Reducers, in theory, are pure functions, according to the documentation itself.

Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.

So, where should you apply async calls in Redux?
The Actions should be the immediate answer, but the basic implementation of actions is nothing more than a plain JavaScript object you use to pass information to your store. For this reason, the community came up with several middlewares that wrap all the logic into functions instead, which mimic the natural behavior of the store.

Which Async Redux Middleware should you pick?

As with everything in programming, there isn't a one size fits all solution. So you should definitely research which middleware fits your problem the best. The first solution suggested by the documentation is Redux Thunk. This middleware allows you to create Actions as more than plain objects. These new Actions can dispatch other Actions, other Thunks and also perform async operations inside them. But recently, other middlewares have started gaining traction, like Redux-Saga and Redux-Observable have different use cases but they all share one thing, which is a very active repository and thriving community behind them.

Why Redux Thunk?

Out of all the popular solutions to this issue, Redux Thunk is the easiest one to understand. It’s also fairly accessible in technical terms and, at this time, it’s the suggested way to approach this situation according to the Redux documentation.

How to implement Redux Thunk?

We start here:
npx react-create-app my-app --template redux

This will get you a fresh new app using React with all the Redux modules we needed to do this short tutorial. We will also be working with the WoofBot API service.

The first thing to do is to set up a dog slice, where you’ll keep all the information related to your dog API response.

Store Slice

We have our Actions:

  • uploadBreeds: Will be used as a dump of all the payload information regarding the dog breeds.

  • uploadBreedImage: Will be used to uploading certain breeds specific images, if needed.

  • loadingState: Will be used to updating the status of the request.

and Selectors:

  • selectBreeds: Returns an array of all the breeds in store.

  • selectBreedImage: Returns the image for a specific breed.

  • isLoading: Returns the status of the request

How would you normally implement this back and forth with the API and the store? I would fit it all into a useEffect hook, similar to this one:

useEffect-2

What are we doing here?

  1. Component is mounted
  2. Loading State is set to Request with one dispatch of an action
  3. Data is requested using a simple fetch
  4. Data is received from the API and processed to the object we want
  5. Breed information in the slice is set to what we got using another dispatch
  6. Loading State is set back to Waiting

Alternatively, we might receive an error from the API which will stop the flow in step 4 and set the Loading State to Error.

This works, and that’s ok. But it also has several downsides. Mainly, it puts too much logic in the component. It’s not reusable, and if you want to access this information somewhere else, you’ll always need to make sure this component is loaded first.

Now with Thunks:

npm install --save redux-thunk

The component logic looks like this:

useEffectFinal-2

We need to create a new fetchBreeds action that looks very similar to the logic we previously had in the component:

thunk1-3

This simple change of location in the code fixes most of the issues we had previously. We’ve abstracted code from the component and we’ve made this specific piece of logic re-usable throughout the entire code base. This information isn’t bound to mounting the component anymore, so now you can issue a new fetchBreeds action and the data will be loaded.

thunk2-2

This also enables us to chain Thunks in case need more complicated logic inside our actions. We can also access the state directly, instead of needing selectors. However, you’ll still want to use selectors to make sure that any changes to Redux don’t affect your Thunks.

In the end, what to pick to handle async operations in Redux?

Depends on the situation. In the same way you can't use a spoon for everything, the solution has to be chosen based on the problem and not on the other way around.

In the most recent issues I've faced, Thunks was more than enough to satisfy all my edge cases. But maybe in your case, you'll need a more specific solution.

Do your research, keep yourself updated and you'll always have the best tool on your side.

Found this article useful? You might like these ones too!

At Imaginary Cloud, we simplify complex systems, delivering interfaces that users love. If you’ve enjoyed this article, you will certainly enjoy our newsletter, which may be subscribed below. Take this chance to also check our latest work and, if there is any project that you think we can help with, feel free to reach us. We look forward to hearing from you!