Advanced Patterns for API Management in Large-Scale React Applications

Rate this content
Bookmark

In this talk, you will discover how to manage async operations and request cancellation implementing a maintainable and scalable API layer and enhancing it with de-coupled cancellation logic. You will also learn how to handle different API states in a clean and flexible manner.

20 min
25 Oct, 2021

Video Summary and Transcription

This Talk covers advanced patterns for API management in large-scale React applications. It introduces the concept of an API layer to manage API requests in a more organized and maintainable way. The benefits of using an API layer include improved maintainability, scalability, flexibility, and code reusability. The Talk also explores how to handle API states and statuses in React, and provides examples of canceling requests with Axios and React Query. Additionally, it explains how to use the API layer with React Query for simplified API management.

Available in Español

1. Introduction to API Management in React

Short description:

Welcome to Advanced Patterns for API Management in Large-Scale React Applications. Today, we'll cover managing API requests in React, handling different API states, creating custom hooks, canceling requests with Axios and React query. Performing API requests in React can lead to code duplication and lack of reusability. To address this, we can implement an API layer. By creating a base API file and importing it into other API files, we can manage API requests in a more organized and maintainable way.

Hello, and welcome to Advanced Patterns for API Management in Large-Scale React Applications. My name is Tomasz Fienli and I'm a full-stack web and mobile developer with nine years of programming experience. I'm a co-owner of Fienli web tech and a mentor and consultant at CodeMentor.io. I'm also the author of Vue and React the Road to Enterprise books, as well as a technical writer for Telerik and the Road to Enterprise blogs.

Now, let's have a look at what we are going to cover today. So, first of all, we will start with how to manage API requests in React in a scalable and flexible manner with an API layer. Then we will see how to handle different API states while performing API requests. We'll also create custom hooks to manage API requests and states, as well as how to cancel requests with Axios and an API layer. Finally, we'll have a look at how to use React query and API layer and how to cancel requests with them.

Okay, so, how can we perform API requests in React? Actually, it's quite simple, right? We can, for instance, use Axios for that. We can just import it and use it in our components. And, well, it can work fine for, let's say, small applications. But there are some issues as the project grows, especially large ones, really. So, what are the main problems with this approach? First of all, code duplication and lack of reusability. Because imagine that we need to, for instance, fetch information about a user, let's say on logging page, register page, and the user profile page. So, all of these would have, well, the same code snippet. So, Axios gets a URL endpoint and so on and so on, right? So, that's not really reusable. And also, well, it's hard to maintain. Because if we had to make any changes to this code, like let's say, for instance, the URL endpoint change or we had to change the payload format, or, let's say, we had to migrate from using Axios to something like Firebase, right? We would need to visit every single component that is Axios and change them. Well, that's not really great, to be honest. So, let's have a look at how we can fix these issues by implementing an API layer. So, first, we would start with a base API file, where we would import Axios and create a new instance with some default configuration, like, for instance, a base URL. Then we have an API function that basically returns API wrapper methods around our Http client, in this case, Axios. And finally, we export it. Next, what we can do is, we can use this base API file and import it in another API file. So, for example, in this case, we'd have users API file. Because we want to do some things with users, like fetch information about users, add a new user, and so on. In this example, we have three methods, list users, get user, and add user. And as you can see, they all use the API methods from the base API file. And how would we use this in a component now? So, basically, what we would do is, we would import an API method from the API directory, and then just use it in a component.

2. API Layer Benefits and Flexibility

Short description:

The API layer in React allows you to abstract away the details of API endpoints and easily switch between different HTTP clients. By separating the API logic into a separate layer, your components remain agnostic to the underlying implementation. This improves maintainability, scalability, and flexibility. You can easily add new API methods, replace the HTTP client, and enhance the API layer with custom logic. Code reusability is also increased, as API methods can be imported and used anywhere in the application. The API layer pattern is framework agnostic, making it suitable for various development environments.

So, as you can see, the component doesn't have to think about what kind of API endpoint has to be hit, right? The API method takes care of that. So, what about if we wanted to use a different HTTP client, like let's say, for instance, Firebase instead of Axios. So, our base API file would look a bit different. So, here, we would just import some necessary Firebase methods, would initialize the Firebase app. And then we would export what we need, for instance, Firebase, Firestore, which gives us methods to basically, well, connecting with the database, and so on, and other things like off storage functions, etc.

Now, in the user API file, we would again have the same methods, like list users, getUser, and addUser. However, in this case, we're using Firebase, of course, right under the hood. But let's have a look at how it would look like from the components perspective now. Well, actually, for the component, nothing would change, because it still would import the API method from the user's API file, and it would just execute it, right? So that's a really great thing about the API layer, because it's like a black box to your components. Because your components don't really have to care about what you use to perform requests. It only is concerned with what methods you should call, what kind of input it should provide, and what kind of output it can expect. As long as you can preserve this input and output contract, you don't need to make any changes to your components. You only need to make changes to the API layer, really.

Now, let's have a look at the benefits of the API layer. First of all, maintainability, as all the API-related code is in one place. Scalability, as you can easily add new API methods and files. And we also have flexibility, it's much easier to replace the HTTP client, and also enhance the API layer with custom logic. So as you've just seen, we replaced access with Firebase and we didn't need to make any changes to the component. Also code reusability, because API methods can be just imported and used anywhere in the application. And the API layer pattern is also framework agnostic.

3. Handling API States in React

Short description:

Let's explore how to handle API states in React applications. Using Boolean flags for each state can lead to code duplication and complexity. We can improve this by using API states and implementing a useApiStateUseHook. The four API states are Idle, Pending, Success, and Error. By abstracting the states, we can manage API requests more efficiently.

Okay, so next let's have a look at how we can handle API states. So what I've seen in a lot of application is basically using Boolean flags. For instance, if you want to show a spinner, you would have isLoadingState. If you want to show an error, if for instance request failed, you would have an isError flag. And let's see if we want to lately initialize a request. For instance, if a user clicks a button, or if a user scrolls to a certain element, then we can also have isIinitialized flag. So as you can see here, we have three different states, and then all of them are updated accordingly.

The problem, however, and here as you can see, if it was initialized, it would display a button. If it's loading, we display a loading data message, canDisplaySpinner. If there was an error, then there was a problem. And finally, if everything went all right, we are displaying the data. But yeah. The problem, however, is that for each API state, we need a new state hook, right? Because we have initialized, it's loading, it's error. So that's already three states. That's only for one API request. If we need to make two requests, we might need to have six states. If we need to make three requests, we might need nine, and so on, and so on. But that's not really great.

So let's have a look at how we can improve it by using API states instead. And we will also implement a useApiStateUseHook to help us with that. So first of all, we have four different API states. Idle, which means that basically the request didn't start yet. And we have pending, which means that the request is being performed. And then we also have success and error. So obviously, success is for when request completed successfully, and error if there was a problem. We also have an array here, which basically just exports the constants, as we will need it in a moment. So let's go now to useApiStateUseHook. So first of all, we need to import useState and useMemo. And we also get the idle constant, as does our initial state for the hook. And we also get default statuses.

4. Managing API Statuses in React

Short description:

We can use the useApiStateUseHook to manage API statuses in React. By using this hook, we can have a single state that holds all the API statuses, such as idle, pending, success, and error. We can easily update the API status using the setApiStatus method. By implementing a custom useAPI hook, we can further improve the API management in React. This hook accepts a method for executing API requests and a config object for setting initial data on the state.

And the reason why we need them is because we want to basically take all our statuses and then basically return an object with the statuses, like its idle, its pending, its success, its error, as you can see on the right side of the slide. And only one of them will be active at the time. So for instance, at the start, only isIdle will be set to true.

And now, here's our useApiStateUseHook. So we have our state for it. So in comparison to boolean flags, we have only one state that holds all the API statuses. And only one of them can be active at the same time. Then, we have the result of prepareStateUses. As I mentioned, it's an object that has its idle, its pending, and so on. And we use useMemo here so that it only re-evaluates if the API status changes. And then finally, we return an object with apiStatus, setApiStatus method, and all the normalized statuses.

Now, let's have a look at how we can use it. So as you can see here, we basically initialize the useApiStatus hook, and we can pass the idle there, though it's the default state anyway. If we wanted to start with pending immediately, you can do that. And then we destructure all the statuses, as well as the setApiStatus method, which then is used to basically update this API status accordingly. Before the request is started, we set it to pending. If it's completed successfully, we set it to success. If there's a problem, we set it to an error. And as you can see in the markup, we can basically just add ternaries, right? If it's idle, then we do something. If it's pending, then we show the spinner or loading message. If it's error, then an error, and so on and so on. So it's much cleaner this way. We don't need to have as many boolean flags. And the code is much cleaner and more concise.

So now let's have a look at how we can improve it even further by implementing a custom useAPI hook. So, first of all, we import useState again, as well as the useApiStatus hook that we've just covered, and three apiStatuses. So, pending, success, and error. Then useAPI accepts two parameters. The first one is the method that will execute an API request, and the second one is a config object. So, for instance, in this example, we can pass initial data inside of the config object and set it on the state for the data.

5. Managing API States and Requests

Short description:

Inside the useAPI hook, the exec function handles setting API statuses, updating them based on the request status, and managing data and error states. The useAPI hook returns an object with data, API status, error, and exec method.

Besides the data state, we also have a state for the error, and of course we initialize the useApiStatus hook. Now, next inside of useAPI hook, we have the exec function. So, as you can see, what it does, it takes care of basically setting API statuses and updating them accordingly, depending on the status of the request. It also takes care of setting the data after the request finished successfully, and it will also handle setting the error if there was a problem, and it will clear it out if a request is supposed to be started again. And finally, the exec method returns an object with data and error if we needed to handle them. And last but not least, just return everything from the use API hook data, API status error exec and so on.

6. Using the API Hook and Canceling Requests

Short description:

To use this hook, import it and pass the method that executes the API request. The hook returns an object with the API statuses, data, and an exec method. Depending on the status, appropriate markup is returned. To cancel requests with Axios and the API layer, enhance the API layer with abort logic. The with abort method accepts an Axios method and returns a function. The function creates a cancel method and token, enhances the config object, and executes the request. If there's an error, the isCancel method checks if the request was canceled. Wrap API wrapper methods with abort to complete the setup. For example, in a search box feature, an API request can be made for autocomplete.

And now, how can we use this hook? So, obviously, we need to import it and then just pass the method that is supposed to execute the API request. So, in this case, we're passing a method that will just return the result of getUser, which obviously comes from the API layer. And from the use API, we receive an object from which we destructure the normal statuses, the data and the exec method. And as you can see in the JSX, again, depending on the status, we just return appropriate markup. So, you know, for each idle, the button to initialize fetching for spending the spinner and so on and so on.

Okay, now, so how can we cancel requests when using Axios with the API layer? So, actually, we can enhance the API layer with abort logic. So, in the base API file, we would add the with abort method that would first accept the function. So, this function would be one of Axios methods, like Axios.get, Axios.put, post, and so on. Then, with abort returns a function, which, well, basically these arguments should be the ones that are passed to Axios method. So, for instance, URL, body, and config. And what we're doing there is we need to get access to the original config. And the reason for it is because as part of this config, we want to pass an abort property, which should have a function as a value. And what we're doing here, if abort is a function indeed, then we create a new cancel method and the cancel token using Axios' source method, and on the config object, we assign this cancel token, and, finally, we execute the abort method and pass the canceler there. And you will see what we did in a moment. And finally, after enhancing the config object, we just execute the request. We forward all the parameters besides the last one. The reason for it is because we don't want to pass the original config with the abort property, but rather we want to pass our own enhanced config object that doesn't have the abort property but might have a cancel token. And note that it's important that we use await here because if there was an error... Basically, without await, this error wouldn't be caught in the context of with abort. And so that's why we need await here. And then finally, if there's an error, we use access.isCancel method to check if the request was canceled. And if it was, we basically set an aborted property on the error object and we just throw the error feather so it can be handled outside. So let's... Okay, one more thing. We also need to wrap, of course, our API wrapper methods with abort. So like I mentioned before, we first pass access methods and then we initialize it again and forward all the parameters.

Okay, so here's how we can use it. Now, so imagine basically a feature like a search box. For instance, user can enter some query and there will be an API request made to fetch some information. For instance, for autocomplete.

7. Storing Cancellers and Handling Errors

Short description:

To store the canceller method between re-renders, we use the ref. The optional chaining operator ensures that the canceller method is executed if it exists. The abort property in the config object receives the canceller method as an argument and assigns it to the ref. The catch statement checks if the error was aborted. The implementation details of the HTTP client are abstracted away, and we only need to provide the abort property.

So we have two states, one for the data that will be fetched from the API and one for the query that the user enters. And we also have the search about ref. So that's an important part here. Because the thing is that we need to be able to store the canceller method in between re-renders, right? So that's why we will put it on the ref. But first, let's just have a look at the onQueryChange function.

So what's happening here, well, obviously we're setting the input value on the query, but that's not the important part here really. But here, before the request is initialized, we're trying to cancel the previous one. However, there might be no canceller methods when the onQueryChange is initialized for the first time. So that's why we use the optional chaining operator. So basically, if there's a canceller method, execute it. But if it's not, then that's fine. We don't care, but we don't want the JavaScript engine to throw an error here, right? So that's why we use the optional chain operator. And then next, as the part here, the abort property. So basically, as part of the config object, we pass this abort property that receives a function. And the canceller method is passed as the first argument. And then we can basically assign this canceller on the ref. So that when this onQueryChange is initialized again, we will have access to this canceller. And finally, in the catch statement, we basically check if the error was aborted. And it was. And what's really great about the way it is implemented is that basically the implementation details of the HTTP client, which is accessed in this case, didn't leak into our component at all. We didn't have to import it anywhere. Rather, we only have to provide the abort property. And that's where we get the canceller method. Set it on the ref, and voila, it's available for us. And we can just call it.

8. Using the API Layer with React Query

Short description:

Using the API layer with React query is simple. Import the API method and the necessary React query hook, like useQuery. Pass the query key and the API method, then destructure the required data, refetch method, and statuses. Render content based on the status.

Okay, so now let's have a look at how we can use the API layer with React query. But actually, it's quite simple, because really what we only need to do is import the API method from the API layer. Also import whichever hook we need from React query, like for instance, use query in this case. And then we just use it. Right? We pass the key for the query, then we pass our list. Well, in this case, it lists users. So the API method. And we just disstracture what we need. So for instance, in this case, we get data. We get the refetch method, which can be used to initialize the request. And we also get our normal statuses. Again, and this part is basically the same as it was with, let's say, with our own use API hook, right? And depending on the status, we render appropriate content.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Summit 2022React Summit 2022
20 min
Routing in React 18 and Beyond
Top Content
Concurrent React and Server Components are changing the way we think about routing, rendering, and fetching in web applications. Next.js recently shared part of its vision to help developers adopt these new React features and take advantage of the benefits they unlock.In this talk, we’ll explore the past, present and future of routing in front-end applications and discuss how new features in React and Next.js can help us architect more performant and feature-rich applications.
TechLead Conference 2023TechLead Conference 2023
35 min
A Framework for Managing Technical Debt
Top Content
Let’s face it: technical debt is inevitable and rewriting your code every 6 months is not an option. Refactoring is a complex topic that doesn't have a one-size-fits-all solution. Frontend applications are particularly sensitive because of frequent requirements and user flows changes. New abstractions, updated patterns and cleaning up those old functions - it all sounds great on paper, but it often fails in practice: todos accumulate, tickets end up rotting in the backlog and legacy code crops up in every corner of your codebase. So a process of continuous refactoring is the only weapon you have against tech debt.In the past three years, I’ve been exploring different strategies and processes for refactoring code. In this talk I will describe the key components of a framework for tackling refactoring and I will share some of the learnings accumulated along the way. Hopefully, this will help you in your quest of improving the code quality of your codebases.

React Day Berlin 2022React Day Berlin 2022
29 min
Fighting Technical Debt With Continuous Refactoring
Top Content
Let’s face it: technical debt is inevitable and rewriting your code every 6 months is not an option. Refactoring is a complex topic that doesn't have a one-size-fits-all solution. Frontend applications are particularly sensitive because of frequent requirements and user flows changes. New abstractions, updated patterns and cleaning up those old functions - it all sounds great on paper, but it often fails in practice: todos accumulate, tickets end up rotting in the backlog and legacy code crops up in every corner of your codebase. So a process of continuous refactoring is the only weapon you have against tech debt. In the past three years, I’ve been exploring different strategies and processes for refactoring code. In this talk I will describe the key components of a framework for tackling refactoring and I will share some of the learnings accumulated along the way. Hopefully, this will help you in your quest of improving the code quality of your codebases.
React Advanced Conference 2022React Advanced Conference 2022
22 min
Monolith to Micro-Frontends
Top Content
Many companies worldwide are considering adopting Micro-Frontends to improve business agility and scale, however, there are many unknowns when it comes to what the migration path looks like in practice. In this talk, I will discuss the steps required to successfully migrate a monolithic React Application into a more modular decoupled frontend architecture.
React Day Berlin 2022React Day Berlin 2022
29 min
Get rid of your API schemas with tRPC
Do you know we can replace API schemas with a lightweight and type-safe library? With tRPC you can easily replace GraphQL or REST with inferred shapes without schemas or code generation. In this talk we will understand the benefit of tRPC and how apply it in a NextJs application. If you want reduce your project complexity you can't miss this talk.
GraphQL Galaxy 2022GraphQL Galaxy 2022
16 min
Step aside resolvers: a new approach to GraphQL execution
Though GraphQL is declarative, resolvers operate field-by-field, layer-by-layer, often resulting in unnecessary work for your business logic even when using techniques such as DataLoader. In this talk, Benjie will introduce his vision for a new general-purpose GraphQL execution strategy whose holistic approach could lead to significant efficiency and scalability gains for all GraphQL APIs.

Workshops on related topic

React Summit 2023React Summit 2023
170 min
React Performance Debugging Masterclass
Top Content
Featured WorkshopFree
Ivan’s first attempts at performance debugging were chaotic. He would see a slow interaction, try a random optimization, see that it didn't help, and keep trying other optimizations until he found the right one (or gave up).
Back then, Ivan didn’t know how to use performance devtools well. He would do a recording in Chrome DevTools or React Profiler, poke around it, try clicking random things, and then close it in frustration a few minutes later. Now, Ivan knows exactly where and what to look for. And in this workshop, Ivan will teach you that too.
Here’s how this is going to work. We’ll take a slow app → debug it (using tools like Chrome DevTools, React Profiler, and why-did-you-render) → pinpoint the bottleneck → and then repeat, several times more. We won’t talk about the solutions (in 90% of the cases, it’s just the ol’ regular useMemo() or memo()). But we’ll talk about everything that comes before – and learn how to analyze any React performance problem, step by step.
(Note: This workshop is best suited for engineers who are already familiar with how useMemo() and memo() work – but want to get better at using the performance tools around React. Also, we’ll be covering interaction performance, not load speed, so you won’t hear a word about Lighthouse 🤐)
GraphQL Galaxy 2021GraphQL Galaxy 2021
48 min
Building GraphQL APIs on top of Ethereum with The Graph
WorkshopFree
The Graph is an indexing protocol for querying networks like Ethereum, IPFS, and other blockchains. Anyone can build and publish open APIs, called subgraphs, making data easily accessible.

In this workshop you’ll learn how to build a subgraph that indexes NFT blockchain data from the Foundation smart contract. We’ll deploy the API, and learn how to perform queries to retrieve data using various types of data access patterns, implementing filters and sorting.

By the end of the workshop, you should understand how to build and deploy performant APIs to The Graph to index data from any smart contract deployed to Ethereum.
React Summit 2022React Summit 2022
147 min
Hands-on with AG Grid's React Data Grid
WorkshopFree
Get started with AG Grid React Data Grid with a hands-on tutorial from the core team that will take you through the steps of creating your first grid, including how to configure the grid with simple properties and custom components. AG Grid community edition is completely free to use in commercial applications, so you'll learn a powerful tool that you can immediately add to your projects. You'll also discover how to load data into the grid and different ways to add custom rendering to the grid. By the end of the workshop, you will have created an AG Grid React Data Grid and customized with functional React components.- Getting started and installing AG Grid- Configuring sorting, filtering, pagination- Loading data into the grid- The grid API- Using hooks and functional components with AG Grid- Capabilities of the free community edition of AG Grid- Customizing the grid with React Components
Node Congress 2022Node Congress 2022
98 min
Database Workflows & API Development with Prisma
WorkshopFree
Prisma is an open-source ORM for Node.js and TypeScript. In this workshop, you’ll learn the fundamental Prisma workflows to model data, perform database migrations and query the database to read and write data. You’ll also learn how Prisma fits into your application stack, building a REST API and a GraphQL API from scratch using SQLite as the database.
Table of contents:
- Setting up Prisma, data modeling & migrations- Exploring Prisma Client to query the database- Building REST API routes with Express- Building a GraphQL API with Apollo Server
React Advanced Conference 2022React Advanced Conference 2022
206 min
Best Practices and Patterns for Managing API Requests and States
Workshop
With the rise of frameworks, such as React, Vue or Angular, the way websites are built changed over the years. Modern applications can be very dynamic and perform multiple API requests to populate a website with fresh content or submit new data to a server. However, this paradigm shift introduced new problems developers need to deal with. When an API request is pending, succeeds, or fails, a user should be presented with meaningful feedback. Other problems can comprise API data caching or syncing the client state with the server. All of these problems require solutions that need to be coded, but these can quickly get out of hand and result in a codebase that is hard to extend and maintain. In this workshop, we will cover how to handle API requests, API states and request cancellation by implementing an API Layer and combining it with React-Query.
Prerequisites: To make the most out of this workshop, you should be familiar with React and Hooks, such as useState, useEffect, etc. If you would like to code along, make sure you have Git, a code editor, Node, and npm installed on your machine.