Proper Data Service Architecture in React

How to be ready and properly prepare your codebase for changes in the React world

Przemyslaw Nowak
JavaScript in Plain English

--

Photo by Ryan Quintal on Unsplash

Many times developers struggle with building proper data services. At the beginning, we got React class Services → which were quickly transited to Redux and Sagas to easily handle side effects and “react” to loading states changes but it became overwhelming in case of learning and understanding what is going on in the life cycle, then hooks came to the game with magic useEffect(), and when we get to use it new smarter libraries like React Query comes in. How we can prepare our apps for those changes in React world? Do we can do it cheap and safe without the need to rewrite half of the codebase? In this article, I will show you one simple way how to be ready and properly prepare your codebase for change.

Coupling problem

Let’s start with the coupling problem. What do I mean by that? All issues described above comes from the wrong usage of mentioned libraries. I’m not saying that any of those technologies are good or bad, probably all of them were or are good at some point, the real issue is the usage of those libraries! I know, I know… everyone reads documentation and doing their best, but the real issue is that those technologies are used directly in application — it boils down to the coupling problem.

Let’s take a look at an example of rewriting Redux Saga to ReactQuery, we have that code:

You can imagine how many places and additional reducer, actions, and sagas files you have. So now you need to refactor it — and if all of the code was created like this one — we always have data , loading and it is directly taken from the state via useSelector we can start thinking that it is pretty generic and we can write some codemod to update it to a new way of data service (e.g React Query). But we all know that reality is different, and some developers do their own utils to select something e.g., useSelector(selectFunction), etc. all of those cases become too complex for codemods, so we end up with manual rewriting all of the places. So what we need to do is to change this code into:

When we go through all the files and finally refactor it, we can open the champagne (of course if our boss won’t ask us why we spent much time on some invisible refactor). But then… React Query changed their API and now useQuery hook returns an array, not an object, e.g., [data, loading, error] — ok, that’s not so bad, we can still create codemod, of course before that we need to explain it to our boss and ask for time for an update because all of us want to work with updated libraries. But we all know how fast the JS world changes, so we can assume that the new “super cooler” library comes in. And then we need to go to our boss again and…

ask for time for refactoring.

Solution

Maybe you can think of a solution, I have a really simple one — let’s create our own API and abstract away library. So simple right? Let’s imagine how simple refactor would be if we had our service hook package with company-wide API? Let’s do this in example and let’s transform our Redux code to a custom service hook:

This is really important to move this logic to a separate hook, and more important is to keep ALWAYS the same output shape — because it will save us a lot of time, let’s use it in our component:

Now, we would like to migrate from Redux to React Query, what you can see we can do this pretty easily because only what we need to change is our service hook:

And the component would look the same, there is no need to do changes in component! Only what we need to change is our service hook.

As you can see this is really cool because no matter if React Query API will change or if we would like to migrate from one technology to another one — we have only one layer to test and rewrite. Moreover, you can imagine that one team delivers services and the second one just uses it from the service library, it is really easy because we always know what API we will have and from the developer's point of view, we don’t care what is used under the hood — if it is React Query, Redux Saga, or something totally different.

Summary

I showed you a really simple solution to the code migration issue — we all know that sometimes abstractions can be overwhelming, and to be clear it is not a pattern for everything. It is a well-tested pattern to create a service hooks package and expose one API (and it’s important this API should be like a contract for us and shouldn’t change). Thanks to that we have only one layer to test and we can reduce the blast radius to a minimum across the whole company.

You can follow me on Twitter, thanks for reading!

More content at PlainEnglish.io. Sign up for our free weekly newsletter. Follow us on Twitter and LinkedIn. Check out our Community Discord and join our Talent Collective.

--

--