Search code examples
axiosvuejs3lifecyclepinia

How to get data from an API only once (on app creation, outside component or view) in Vue3 SPA, with Pinia store


Is it possible and is it a good practice to avoid fetching data from an API every time the router view is loaded or the component is Mounted?

The thing is that some data rarely changes (like a dropdown options list, imagine allowed animal picks for my app) and it's logical not to send a request every time to a server, instead on app creation would be sufficient.

Tried in App.vue, is that a common thing?

IN APP.vue

import { computed, onMounted, onUpdated, ref } from 'vue';
onMounted(()=>{
axios.get('/data')....

.then((res)=>{
store.property = res.data
...
})

})

Solution

  • I think having it on mount in the App.vue component is acceptable since the App component would not be remounted.

    The ideal setup, however, depends on some other parameters like size of application and size of team that's maintaining it. In a large applications you might want to organize things in amore structured and consistent way so you and other folks working on the code know where to find things.

    You could consider moving the API call into the pinia action.

    store.loadMyData()
    
    // instead of
    
    axios.get('/data')
      .then((res)=>{
        store.property = res.data;
      })
    

    That way you have fewer lines of code in the component. Having "lean" components and moving "business logic" out of components usually makes for better organization which makes it easier to maintain.

    Within the action, you can track the state of the API

    const STATES = {
      INIT: 0,
      DONE: 1,
      WIP: 2,
      ERROR: 3
    }
    export const useMyApiStore = defineStore('myapi', {
      state: () => ({
        faves: [],
        favesState: STATES.INIT
      }),
      actions: {
        loadMyData() {
          this.store.favesState = STATES.WIP;
          axios.get('/data')
            .then((res) => {
              this.store.property = res.data;
              this.store.favesState = STATES.DONE;
            })
            .catch((e) => {
              this.store.favesState = STATES.ERROR;
            })
        },
      },
      getters: {
        isLoaded(){
          return this.store.favesState === STATES.DONE;
        }
        isLoading(){
          return this.store.favesState === STATES.WIP;
        }
      }
    })
    

    This is, obviously, more verbose, but allows for the components to be smaller and contain less logic. Then, for example, in your component you can use the getter isLoading to display a loading indicator, or use isLoaded inside another component to determine whether to show it.