DRY Patterns for API Calls in React
In most of the React projects, we have components where we need to fetch the data from an API on the mount. React provides us with lifecycle methods, and hooks to accomplish that but usually, we end up duplicating the same code for handling the API call states in all of those components. In this article, we will talk about how can we avoid that by following DRY code principles using different React constructs.
The Problem
Each API call in an application has three states:
- Loading — When the call is made.
- Successful — When the API returns a successful response.
- Error — When the call fails.
Let’s imagine that we are creating a very basic e-commerce application that has a Catalog
page that shows the list of products available and a Cart
page that shows the products added to the cart by the user. Both of these pages need to fetch the data from an API on the mount.
Let's get started by creating the API calls to fetch the products and the cart.
Using Class-Based Components
React class-based components give a lifecycle method named componentDidMount
to execute the code only once after the initial render. This makes the method best suited for API calls on the mount.
Using the same method, we can create the Catalog and Cart Components as:
Using Functional Components
React@16.8 came up with the concept of useState
a hook using which we can maintain the state in functional components as well. Similar to componentDidMount
, useEffect
hook with empty dependencies array can be used to make an API call only once after the initial render.
Thus by using the functional components, we can create the Catalog and Cart Components as:
More information about react hooks can be found here.
No matter if you are using the class-based components or functional components, we can see a lot of code duplication.
- Both components have duplicated states to handle the API call state.
- componentDidMount or useEffect hooks have the same code to fetch the data in both the components.
- Both components have duplicated code in the render method to show the user experience depending upon the API state.
Solution
Refactoring using Higher Order Components
The concept of Higher order components is taken from Higher order functions in functional programming. The difference is, that instead of taking a function as an argument and returning a function, it takes a react component as an argument and returns another react component.
Using the concept, we can create a HOC named withApiCallOnMount
which can take the component needed to make the API call on the mount and the service as arguments and return a component with the updated API states as props.
To use the HOC, Catalog and Cart component will change as:
If we notice, both the components are changed to stateless functional components now as the state logic is moved to a single HOC.
More information about HOCs can be found here
Refactoring using Custom Hooks
We can use the concept of React hooks to create custom hooks to extract the common code. A custom hook can use the predefined hooks and thus the consumer component can directly use the custom hooks instead of repeating the predefined hooks.
Using the concept, we can create a custom hook named useApiCallOnMount
which will take service as an argument and return the API states to the consumer component.
To use the custom hook, the Catalog and Cart component will change as:
More information about custom hooks can be found here.
In both the above two refactorings using HOC and Custom hook, we have also solved the problem of contradicting states. More information about the same can be found here.
ApiStateHandler as a reusable component
The last thing we need to worry about is the duplicated code in the render method. We are writing the same stuff in both the components to return the JSX according to the API state.
To resolve this, we can create another react component, which takes the API states as props and render the appropriate JSX according to the passed prop.
To use the ApiStateHandler component, Catalog and Cart components will change as:
In the end, if your application needs more than a simple solution to fetch and update but even caching the response and many more, you can always try the well-known libraries React Query or SWR as one in all solutions for you.
A lot more patterns of refactoring a react app can be found in this Github repo.