Use Composition API to easily handle API requests in Vue.js
When building a web-app most of the times we need to fetch from the server or execute some action.
Handling the request status can be tedious and we often write the same code over and over.
With the composition API we can write a composable that handles the request status and exposes reactive objects
Let start by defining some requirements:
- Needs to provide reactive
isLoading
,result
,error
andexecute
isLoading
:true
is we are waiting for a requestresult
: Success response from the servererror
: Error object form the serverexecute
: Execute the request
- Need to provide the base request and the
execute
should allow sendingparams
orbody
to the request.
The expected usage will be like:
setup(){
const userList = useApi(()=>({url:`https://swapi.dev/api/people/1`}), (r)=> r.json());
userList.execute(); // we can await for result
// ...
return { ...userList }
}
This factory based usage allows great flexibility, the first parameter is a factory
that returns the fetchRequest
, the second parameter is just a transformer
of the fetch
result value.
The return execute()
will pass all the parameters to the factory
, allowing to greater customization on building requests.
Allowing you to do:
setup(){
const userList = useApi((page: number)=>({url:`https://swapi.dev/api/people/${page}`}), (r)=> r.json());
userList.execute(1); // we can await for result
// ...
return { ...userList }
}
Allowing greater composability on having automagically having a paginated list:
setup(){
// ...
const page = ref(1);
// watch for `page` changes
watch(page, p=> userList.execute(p));
// ...
return {
...userList,
page
}
}
Simplifying the pagination to changing variable page.value
instead of calling the userList.execute(page.value)
.
# Implementation
A simple javascript implementation (with typings)
function useApi(
factory,
handleResponse
){
const isLoading = ref(false);
const result = ref(null);
const error = ref(null);
const execute = async (...args) => {
const request = factory(...args);
isLoading.value = true;
error.value = null;
try {
const response = await fetch(request);
const valueResponse = await handleResponse(response)
result.value = valueResponse;
return valueResponse;
} catch (e) {
error.value = e;
result.value = null;
} finally {
isLoading.value = false;
}
};
return {
isLoading,
result,
error,
execute,
};
}
This implementation might not yield correct results, if the
execute
is called many times before the first finishes, because theresult
will always be the latest server response.
I'm the creator of a composable library compatible with vue2 + composition-api
and vue3
called vue-composable, where you can do something similar by using usePromise, using it will make sure the result.value
will hold the value of the last valid exec()
response value.
// pass `true` on the second argument to make it lazy
const userList = usePromise((id)=>fetch(`https://swapi.dev/api/people/${id}`).then(r=>r.json(), true)
userList.exec(1)
I've been using this pattern on my projects, from action buttons to fetching the next paginated page, and has been working great so far. It makes it trivial to disable a button if a request is happening or to compose it into other composables.
Related Articles
The new Provide and Inject in Vue 3
Getting stuck into the prop drilling? Learn how provide/inject can make your components more flexible and independent in this short tutorial.
Jul 18, 2022
The 101 guide to Script Setup in Vue 3
Don't you know about Script Setup yet? Check out this short article now and learn the nicest way to define a Vue component right now!
Jun 20, 2022
Going 3D with Trois.js and Vue 3
Learn about Trois.js, a JS library to render 3D scenes in Vue. In this article, we're learning the basics of using Trois.js in a Vite + Vue 3 app
Nov 16, 2021
Sponsors
VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.