The process of creating a useFetch hook to handle asynchronous operations sprinkled with a dose of real-life examples.
Fetching and processing data from an API is one of the most repetitive activities in web applications.
That's why it's a good idea to make sure that the code you write is readable, consistent and easy to understand for others.
Don't count on developers. Everyone has different habits. It will be much easier to create an abstraction and encapsulate repetitive logic inside.
So let's jump into useFetch hook creation process and see how it behaves in a real example.
1. What do you implement?
It will be a small app for loading users from a free API. You will be able to load users and display their avatars. Also, there will be an option to perform requests for details.
In addition, when the request is still pending you will be able to cancel the operation to save some browser resources after clicking Back button.
2. Looking at spaghetti
It works indeed, but the used approach is dirty like a mug.
Try to multiply it by the number of requests in the application or add another code - I wish you luck.
Generally, you can hide this logic in something like useUsersFetch - but implementation inside will be the same.
In addition - it's only users fetch logic. Still, we need to do the same for user details.
Problems with this code:
Flags - easy to add a bug
A lot to test
It's hard to determine what this code does on first look
Lack of single responsibility
You can access data property when it's not ready to be accessed
Repetitive approach and sensitive to a human mistake
Imagine the whole application written like that 😺.
3. Can you do better?
Of course... Just look at the code below and try to understand it. I think it's obvious what is happening immediately.
Look at if statements. Inside useFetch hook we implement later exhaustiveness checking. It will be discussed soon but in simple words - it prevents us from accessing properties when they aren't available and guide us by hand when writing conditional instructions.
Less code = smaller bundle
Option to develop similar features faster
Typescript support when writing if statements
Easy to test and easy to replace
4. Understanding exhaustiveness checking
You must have seen the difference in code and complexity that we discussed earlier. Now you need to understand how to implement this mechanism in Typescript.
It is simple - just create several types that have a common property and make the union of them.
Right now you are not able to access properties other than type without if statement. This protects you from potential bugs and prevents you from adding code in the "other" style.
5. Abort controller
Before we start implementation there is one more thing to explain - AbortController class.
This class must be created one per request. Created instance exposes signal property and abort function. Signal must be passed to fetch.
When you pass the signal property, then you can cancel the request via abort function. You can do it at any time - depends on your needs.
In our case, we call abort all time when the useEffect function is executed.
Also it's good to cancel:
When a user leaves the page
When components unmount
In any other case
When a request is aborted you can see it in the console and network tab.
In network tab
All of this will be possible by the usage of your useFetch hook.
6. Adding tests suites
Let's create useFetch.ts file and same file for tests. You will use TDD. You need to cover the following test scenarios with implementation.
Test implementation is skipped. If you are interested - you can check it in this file or in the example at the end.
7. Implementing useFetch
Firstly, let's start with interfaces.
Next, we need to create a hook and implement a handler function with a state management transition which includes the option to abort a request.
Nothing fancy in this code. You just make generic everything that we discussed before.
8. Create renderer
We can also improve rendering. Instead of several if statements - you can create a component for handling that.
It simplifies rendering. Now it will be the same in the whole application.
9. Implementing our small app
Let's create two hooks that wrap logic for loading users and user details. These hooks will use your newly created useFetch hook.
Now in UsersService we can use signal in fetch.
Right now it's time for the final step. Just use created hooks in the component and display dedicated UI.
I hope you enjoyed this quick win 🙂.
Together we explored a pretty interesting topic of creating abstractions in your code base and dealing with unneeded requests. Right now you can boost your work a little bit.
Feel free to contact me if you have any questions/proposals. Have a nice day and good health!