This project was created to test the TanStack library called Vue Query, the purpose is to test this powerful tool with a Vue project to query an API and implement it in future projects.
VSCode + Volar (and disable Vetur) + TypeScript Vue Plugin (Volar).
TypeScript cannot handle type information for .vue
imports by default, so we replace the tsc
CLI with vue-tsc
for type checking. In editors, we need TypeScript Vue Plugin (Volar) to make the TypeScript language service aware of .vue
types.
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a Take Over Mode that is more performant. You can enable it by the following steps:
- Disable the built-in TypeScript Extension
- Run
Extensions: Show Built-in Extensions
from VSCode's command palette - Find
TypeScript and JavaScript Language Features
, right click and selectDisable (Workspace)
- Run
- Reload the VSCode window by running
Developer: Reload Window
from the command palette.
See Vite Configuration Reference.
npm install
npm run dev
npm run build
Run Unit Tests with Vitest
npm run test:unit
Vue Query is a library designed to simplify and improve the management of data requests in Vue.js applications that interact with RESTful APIs. It provides an easy-to-use interface to query the API, manage related states, and handle data loading logic efficiently.
With Vue Query, developers can easily implement complex data handling logic such as initial loading, periodic updating, and query result caching. The library integrates seamlessly with Vuex, Pinia and follows a hooks-based approach to organize data retrieval and management logic.
Vue Query Documentation: https://tanstack.com/query/latest/docs/framework/vue/installation
-
Simplifies handling of data requests in Vue.js applications.
-
Provides an easy-to-use interface for querying RESTful APIs.
-
Automatically manages the loading, error and success status of queries.
-
Allows flexible configuration of query options such as refresh intervals and data caching.
-
Integrates well with Vuex and Pinia for advanced application state management.
To install Vue Query we must follow the following steps:
With NPM:
npm i @tanstack/vue-query
With Yarn:
yarn add @tanstack/vue-query
We import Vue Query:
import { VueQueryPlugin } from "@tanstack/vue-query";
We do the Vue Query Configuration:
import { createApp } from "vue";
const app = createApp(App);
VueQueryPlugin.install(app, {
queryClientConfig: {
defaultOptions: {
queries: {
staleTime: 1000 * 60, // HOW LONG THE DATA WILL BE CACHED (1000ms * 60s = 1 minute)
refetchOnReconnect: "always", // THIS IS FOR WHEN THE INTERNET GOES OUT AND COMES BACK, IT WILL DETECT THAT THE INTERNET HAS RETURNED AND WILL MAKE A NEW CALL TO THE ENDPOINT
},
},
},
});
The code would look like this:
import "./assets/main.css";
import { createApp } from "vue";
import { createPinia } from "pinia";
import { VueQueryPlugin } from "@tanstack/vue-query";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
VueQueryPlugin.install(app, {
queryClientConfig: {
defaultOptions: {
queries: {
staleTime: 1000 * 60,
refetchOnReconnect: "always",
},
},
},
});
app.use(createPinia());
app.use(router);
app.mount("#app");
For the following example we will use the PokeAPI, we are going to make a GET
request for all the pokemons and for that we are going to point to the following endpoint: https://pokeapi.co/api/v2/pokemon
Para este ejemplo vamos a utilizar Axios:
With NPM:
npm i axios
With Yarn:
yarn add axios
We create an instance of axios src/api/pokeApi.ts
import axios from "axios";
const pokeApi = axios.create({
baseURL: "https://pokeapi.co/api/v2/",
});
export default pokeApi;
We can make a GET
request to https://pokeapi.co/api/v2/pokemon
and create an interface from the resulting JSON.
JSON:
{
"count": 1302,
"next": "https://pokeapi.co/api/v2/pokemon?offset=20&limit=20",
"previous": null,
"results": [
{
"name": "bulbasaur",
"url": "https://pokeapi.co/api/v2/pokemon/1/"
},
{
"name": "ivysaur",
"url": "https://pokeapi.co/api/v2/pokemon/2/"
},
{
"name": "venusaur",
"url": "https://pokeapi.co/api/v2/pokemon/3/"
},
{
"name": "charmander",
"url": "https://pokeapi.co/api/v2/pokemon/4/"
},
{
"name": "charmeleon",
"url": "https://pokeapi.co/api/v2/pokemon/5/"
},
{
"name": "charizard",
"url": "https://pokeapi.co/api/v2/pokemon/6/"
},
{
"name": "squirtle",
"url": "https://pokeapi.co/api/v2/pokemon/7/"
},
{
"name": "wartortle",
"url": "https://pokeapi.co/api/v2/pokemon/8/"
},
{
"name": "blastoise",
"url": "https://pokeapi.co/api/v2/pokemon/9/"
},
{
"name": "caterpie",
"url": "https://pokeapi.co/api/v2/pokemon/10/"
},
{
"name": "metapod",
"url": "https://pokeapi.co/api/v2/pokemon/11/"
},
{
"name": "butterfree",
"url": "https://pokeapi.co/api/v2/pokemon/12/"
},
{
"name": "weedle",
"url": "https://pokeapi.co/api/v2/pokemon/13/"
},
{
"name": "kakuna",
"url": "https://pokeapi.co/api/v2/pokemon/14/"
},
{
"name": "beedrill",
"url": "https://pokeapi.co/api/v2/pokemon/15/"
},
{
"name": "pidgey",
"url": "https://pokeapi.co/api/v2/pokemon/16/"
},
{
"name": "pidgeotto",
"url": "https://pokeapi.co/api/v2/pokemon/17/"
},
{
"name": "pidgeot",
"url": "https://pokeapi.co/api/v2/pokemon/18/"
},
{
"name": "rattata",
"url": "https://pokeapi.co/api/v2/pokemon/19/"
},
{
"name": "raticate",
"url": "https://pokeapi.co/api/v2/pokemon/20/"
}
]
}
We create the interface using QuickType: https://app.quicktype.io/
src/interfaces/pokemon.interface.ts
export interface Pokemons {
count: number;
next: string;
previous: null;
results: Result[];
}
export interface Result {
name: string;
url: string;
}
We create a helper that is used to obtain the list of pokemons src/helpers/getPokemons.ts
import pokeApi from "@/api/pokeApi";
import type { Pokemons } from "@/interfaces/pokemon.interface";
export const getPokemons = async () => {
const { data } = await pokeApi.get<Pokemons>("pokemon");
return data;
};
In my case I created a hook but you can do this in a component or screen that you want.
src/composables/usePokemon.ts
import { getPokemons } from '@/helpers/getPokemons';
import { useQuery } from '@tanstack/vue-query';
export const usePokemon = () => {
const { isPending, isFetching, isError, data:pokemons, error } = useQuery({
queryKey: ['pokemons'],
queryFn: getPokemons,
})
return {
pokemons,
}
}
Finally we use the composable that we just created in a component or screen src/views/HomeView.vue
<script setup lang="ts">
import { usePokemon } from '../composables/usePokemon';
const { pokemons } = usePokemon();
</script>
<template>
<div>
<h1>Pokemon List:</h1>
<ul>
<li v-for="pokemon in pokemons?.results">
{{ pokemon.name }}
</li>
</ul>
</div>
</template>
This is what the application looks like running:
We can see the cache that was created using Google Chrome's Vue Devtools:
The cache name ["pokemons"]
comes from queryKey
of useQuery
which is in src/composables/usePokemon.ts
To see how Vue Query works we must go to the Network section when we inspect the page here we can see all the requests that are made when you enter a page, as we can see in the following example a request was made to the route called pokemon
.
As we configure the time that the data must remain cached in the main.ts
we can go from one page to another and the request will not be made every time you enter the page that requires said data.
staleTime: 1000 * 60 // 1 minute
We configure that the data is kept in cache for 1 minute, so you can exit and re-enter the page and no requests will be made to that route until 1 minute has passed.
Once the minute passes and you exit and re-enter the page you will be able to see that if another request was made to the 'pokemon' route
In the Network tab we can simulate that we do not have internet on the machine, if we select that option we can continue browsing between screens without problem because the data remains cached.
Now in the main.ts
we configure the following line of code:
refetchOnReconnect: 'always'
What this line of code does is that when the Internet is cut off, the application data can still be displayed, but the data that is stored in the cache, when it detects that there is internet again, it will make a new request to update the data that are cached in case there were any changes to the API.
Now what happens if we don't use Vue Query and do it as it is normally done? For this we are going to carry out the following example:
In src/composables/usePokemon.ts
instead of using useQuery we create a reactive variable and pass the data to it using Vue's onMounted method so that when the component is mounted the request is made, this is how it is normally done:
import { getPokemons } from '@/helpers/getPokemons';
import type { Pokemons } from '@/interfaces/pokemon.interface';
import { onMounted, ref } from 'vue';
export const usePokemon = () => {
const pokemons = ref<Pokemons>();
onMounted(async () => {
pokemons.value = await getPokemons();
})
return {
pokemons,
}
}
If we see the screen we can see that it has the same behavior that we saw at the beginning.
But with the inconvenience that every time we leave the page and return, a request will be triggered to 'pokemon' to obtain the list of pokemons, this can be inconvenient in certain cases, for example if the pokeapi were paid and had Limited to only being able to make a few requests, over time the requests we can make will run out, but this also brings more inconveniences such as the excessive number of requests that can make our application have less performance and therefore behave slowly. , among other inconveniences
NOTE: Vue Query internally does make the request to the API even if we do not see it, what happens is that it compares the data it has in cache with the data returned by the API and if there is no data in cache it finishes doing the request and puts that data in cache, that is, it creates it, now as a time has been configured in which the data will be kept in cache, Vue Query internally does make the request to verify that the data returned by the API is equal to the that it has in cache, if in any case the data changed in the API even if the established time that was configured has not passed, Vue Query is responsible for finishing making the request and changing the data that it has in cache, if it has not passed the established time and Vue Query does not detect a change in the data returned by the API, then Vue Query waits the established time to make the request again and apply the corresponding changes