Calling APIs is the most common thing to do in any modern web application. When it comes to talking with an API, we must do many repetitive things like getting data from an API call, handling the success or error case, and so on.

We always have to do those tedious tasks when calling tens of hundreds of API calls. We can handle those things efficiently by putting a higher level of abstraction over those barebone API calls, whereas in some small applications, sometimes we don't even care.

The problem comes when we start adding new features on top of the existing features without handling the API calls in an efficient and reusable manner. In that case, for all of those API calls related repetitions, we end up with a lot of repetitive code across the whole application.

In React, we have different approaches for calling an API. Nowadays, we mostly use React hooks. With React hooks, it's possible to handle API calls in a very clean and consistent way throughout the application in spite of whatever the application size is. So let's see how we can make a clean and reusable API calling layer using React hooks for a simple web application.

I'm using a code sandbox for this blog, which you can get here.

I know the example above isn't the best code, but at least it's a working and valid code. I will try to improve that later. For now, we can just focus on the bare minimum things for calling an API.

Here, you can try to get post data from JsonPlaceholer. Those are the most common steps we follow for calling an API, like requesting data, handling loading, success, and error cases.

If we try to call another API from the same component, how would that look? Let's see.

Now it's going insane! For calling two simple APIs, we've done a lot of duplication. On a top-level view, the component is doing nothing but just making two GET requests and handling the success and error cases. For each request, it's maintaining three states which will periodically increase later if we've more calls.

Let's refactor to make the code more reusable with fewer repetitions.

Step 1: Create a Hook for the Redundant API Request Codes

Most of the repetitions we have done so far are about requesting data, handing the async things, handling errors, success, and loading states. How about encapsulating those things inside a hook?

The only unique things we are doing inside handleComments and handlePosts are calling different endpoints. The rest of the things are pretty much the same. So we can create a hook that will handle the redundant work for us, and from outside, we'll let it know which API to call.

Here, this request function is identical to what we were doing on the handlePosts and handleComments. The only difference is it's calling an async function apiFunc which we will provide as a parameter with this hook. This apiFunc is the only independent thing among any of the API calls we need.

With hooks in action, let's change our old codes in App components, like this:

How about the current code? Isn't it beautiful without any repetitions and duplicate API call handling things?

Let's continue our journey from the current code. We can make App component more elegant. Now it knows a lot of details about the underlying library for the API call. It shouldn't know that. So, here's the next step…

Step 2: One Component Should Take Just One Responsibility

Our App component knows too much about the API calling mechanism. Its responsibility should just be to request the data. How the data will be requested under the hood, it shouldn't care about that.

We will extract the API client-related codes from the App component. Also, we will group all the API request-related codes based on the API resource. Now, this is our API client:

All API calls for comments resource will be in the following file:

All API calls for posts resource are placed in the following file:

Finally, the App component looks like the following:

Now it doesn't know anything about how the APIs get called. Tomorrow if we want to change the API calling library from axios to fetch or anything else, our App component code will not get affected. We can just change the codes form client.js This is the beauty of abstraction.

Apart from the abstraction of API calls, Appcomponent isn't the right place to show the list of posts and comments. It's a high-level component. It shouldn't handle such low-level data interpolation things.

So we should move this data display-related things to another low-level component. Here I placed those directly in the App component just for the demonstration purpose and not to distract with component composition-related things.

Final Thoughts

The React library gives the flexibility to use any kind of third-party library based on the application's needs. As it doesn't have any predefined architecture, different teams/developers adopted different approaches to developing applications with React. There's nothing good or bad. We choose the development practice based on our needs/choices. One thing that is beyond any choices is to writing clean and maintainable codes.