Consuming a REST API with React Hooks (using design patterns)
As a software engineer, it is important to have a forward thinking mindset when designing the architecture for a web application. Sooner or later standards will change or better frameworks will be released (or even worse, currently used frameworks bite the dust). If sound business logic is in place, these changes won’t rock the boat too hard when trying to get new code back to a usable state.
This concept is a key one to grasp and use when developing react web applications. For the sake of this article we will simply focus on how it pertains to requesting data from an API and using it within the application. We will be looking at:
- The basic setup for consuming APIs with react hooks
- Abstracting low level plumbing calls away
- Further abstracting where it makes sense
Basic API Request Flow
There are quite a number of articles available to read which aid in learning how to setup the basic plumbing to make a react app communicate with a REST api, usually something like so…
Not bad, this will get your feet wet in the overall flow of requesting asynchronous data in the react ecosystem, however lacks quite a bit in practicality and here’s why.
- The component above is very tightly coupled to how the data is retrieved, rather than just the data.
- It also doesn’t provide response error handling.
Separation of Concerns
A first step to improve this code would be to extract the bulk of the logic into a separate custom hook so that the consumer isn’t responsible for knowing how or where the data comes from, hence creating more separation of concern.
This provides us looser coupling in the
TodoList, allowing the logic that retrieves the todos to be handled by a more focused, single purpose component.
Abstract Low Level APIs
TodoList is starting to look better, but we still have a few problems with the way the request logic is working in our custom hook. It still requires knowing the plumbing of how to access the data, which should be moved to a lower level considering we are still operating in the business logic realm. Let’s start by abstracting the fetch call into another hook, and while we’re at it let’s add some error handling.
Let’s also create another file to store our request options for different requests, so that we don’t need to dig through a ton of files to change out request URLs.
Then we modify the
useTodos hook to implement the new request abstraction hook like so. Notice we add a
useMemo call so that the request object becomes cached upon creation, thus returning the same reference to the request on every subsequent render. This serves the purpose of preventing extra unnecessary API calls from the
useApiResult custom hook. The reason this works is because the
useEffect hook will check the request variable for changes on every render. Since
request is an object it will check for changes by reference rather than value.
use the useMemo() hook to cache objects and prevent extraneous API calls
Now we can add the changes to display any request errors in the
TodoList that come from the hook.
Finally we are starting to see a flow that allows room for change in the underlying plumbing while allowing the presentation and business logic to remain less affected. However, there is still another step that could be taken to abstract the plumbing just a bit further.
Abstract even Further!
Let’s take the fetch call and wrap it with a unified request interface so that if we decide to use another request library, it will be easier to swap out later. We will then provide this function via a react context and a custom hook that wraps the useContext function. This will make it accessible to any component or hook wrapped within the context provider component. We will also add the context provider component to our app near the top of the component tree to ensure the components have access to the request context.
In short, here’s what we are going to do:
- Create a new context and wrap the App with it’s provider
- Abstract context usage with a custom
- Create a request lib with a unified interface, pass into context’s value
- Use the hook wherever fetch call’s were being made
As you can see, it can be quite a bit of work initially to create separation between the business logic and the logic which performs underlying calls. However, it will payoff in the long run as the application grows and changes in the future.
I hope this information was useful and you’ve learned how to properly perform API requests within a react application, or at least ideas on how to improve your code base using abstraction and separation of concern.