Data fetching in Redux is painful, get rid of it now

Davide Cantelli
5 min readSep 21, 2020

Although I have been using Redux to handle data fetching in many of my projects, I’ve always believed there must be a better way to do the same with React JS.

🤯 Why?

👎 Verbosity
Fetching data in Redux is a quite verbose process. Each fetch operation requires the developer to create:

  • three actions (fetch request, succeeded, failed)
  • three reducers (fetch request, succeeded, failed)
  • one middleware (usually redux-saga or redux-thunk)

Writing all this code takes a lot of time, even for a simple API call. Moreover, it’s not hard to end up with a big JS bundle.

👎 Over usage of Redux store
Every fetch operation performed by Redux has to save its loading and response statuses in the Redux store. Yet, most of the time, the component performing that operation doesn’t need to share its data with other components. Consequently there is no need to store it in the Redux Store.

🤔 So what?

Let’s review a typical Redux process for a fetch operation:

  1. Dispatch an Action fetchSomething() from within the Component
  2. This Action hits the Reducer which will update the store with something like isLoading: true
  3. The Component re-renders and displays a loader
  4. In the meanwhile the Action hits the Middleware which takes care of calling the API
  5. The API returns the response to the Middleware, which dispatches another Action: fetchSomethingSucceeded() when succeeded, or fetchSomethingFailed() when failed
  6. The succeeded/failed Action hits the Reducer, which updates the store
  7. Finally, the response is back to the Component (optionally through a memoized selector) which will show the data

It’s a long process for just a simple fetch, isn’t it?

I would like to take a step back end rethink about the whole thing from a high-level perspective not including Redux. The basic idea is:

  1. Let’s perform the fetch op from the Component and wait for the response
  2. Once we get the response, we update the local state of the Component
  3. Optionally, if we need to save the response (or part of it) in the Redux store, we can do so by dispatching a dedicated Action

With this approach we don’t need to store any information about the fetch operation in Redux. If there is any need to share it, we can store the final result (or part of it ) anyway.

This solution is simpler, but obviously not good enough. There is no separation of concerns, there will be a lot of code duplication, verbose logic within the component, etc.

Let’s continue.

Thanks to React Hooks, we can abstract the fetch operation into a custom and reusable useFetch hook:

Simple and clean, thanks to the “magic” of Hooks.

But do we really need to reinvent the wheel?

Keep reading.

🚀 React Query

React Query is a really cool library.

As stated in the documentation’s overview, React Query makes fetching, caching, synchronising and updating the server state in your React applications a breeze.

React Query drastically simplifies the codebase and enhances user experience, while making it much easier to implement features such as caching, optimistic UI , pagination, lazy loading, and others.

⚠️ React Query is specifically designed to manage any asynchronous state that you get from outside of your app: server-state, API state, or anything else that is not local client state. For the client state we can keep using Redux (or Flux, or Context API, or whatever you want).

ℹ️ It’s very common after existing applications have moved to React Query, that their client state is extremely small and doesn’t even need an external library such as Redux.

Let’s take a look at the same example as above, this time using React Query:

As you can see, the implementation is really similar to what we did before.
The library exports a Hook called useQuery that accepts two arguments:

  1. A unique ID of your choice. This is being used to save the result of the API in the queryCache, a smart local cache managed by React Query. This cache is also accessible through the library’s API.
  2. A function that calls the API and returns a Promise

That’s it.
Easy.

Of course there is much more behind React Query, like ways to perform a POST request (also called mutation), or to implement an infinite scroll, and more. However the purpose of this article was just to stimulate your curiosity and provide you a better alternative to Redux.

The official documentation is a very valid starting point if you want to deepen the subject.

💊 Tips & Tricks

1. Use custom hooks

Never use useQuery inside the Component. Instead, wrap it into a custom hook. This will keep your Component abstracted from the library and increase decoupling, as well as making the query reusable and testable.

Check the example below:

2. Embrace E2E tests

With Redux it is common practice to rely on unit tests since you can test actions, reducers, middleware, and selectors. With React Query all of that is gone. You can unit test hooks and something more, but I guess you will find it useful to start using (if you are not already doing so) end-to-end tests with an amazing library such as Cypress.io.

🙁 Are there any downsides?

Well, yes. Moving away from Redux might introduce some unwanted effects:

  • Flexibility
    In the unfortunate scenario in which you are planning to migrate from React to another front-end library (such as VueJS or AngularJS), you would have to give up all your React + React Query codebase, as the second library strictly depends on the first one. If your codebase was a combination of React + Redux instead, migrating would be an easier process as Redux is just Vanilla JS, thus independent from React.
  • Debugging
    Redux DevTools are really powerful and well integrated with the browser, and even if React Query provides DevTools as well, they are not comparable for the time being. The library is anyway growing really fast, and new features are being implemented regularly.

Conclusions

I believe React Query is an awesome alternative to Redux for what concerns data fetching, even though I understand that switching an existing application over might not be that easy.

Despite the title of this article, every situation is a case in itself. Redux could still be the best choice for you anyway.

If you are hesitating, my advice is to initially try with a small application on which you can do some experiments.

Happy coding! 💻

Thanks to Alessio Van Keulen for proofreading this article.

--

--

Davide Cantelli

Software developer, passionate photographer, design lover and tireless dreamer