theking2
theking2

Reputation: 2842

How to signal a view when pinia store is fully loaded?

We have a item view whose contents depends on a store with is loaded from a (rather slow) api. We found that the view is mounted before the store is fully loaded which is ok but how can a store declare its state loaded? We tried this in the store:

    async function initialize() {
        Promise.all([fetchEquipmentShortlist(), fetchEquipmentList()])
        .then(() => {
            console.log('initialized');

        })
    }

where the initialize() method is called in main.js and the two functions are async/await fetch() functions. What could we put in the .then() resolve function to signal the view to start displaying the contents?

main.js:

import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { de, en } from 'vuetify/locale'

import App from './App.vue'
import router from './router'
import './assets/index.css'

import { useSystemStore } from "@/store/SystemStore.js";
import { useEquipmentStore } from "@/store/EquipmentStore.js";

const mountEl = document.querySelector("#app")
const app = createApp(App, { ...mountEl.dataset })

//Vuetify
import '@mdi/font/css/materialdesignicons.css'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'

const vuetify = createVuetify({
  components,
  directives,
  locale: {
    messages: { de, en }
  }
})

app
  .use(createPinia())
  .use(vuetify)
  .use(router)

// Fetch settings.json
fetch(import.meta.env.BASE_URL + 'config/settings.json')
  .then((response) => response.json())
  .then((config) => {
    for (const key in config) {
      app.provide(key, config[key])
    }
    const equipmentStore = useEquipmentStore()
    equipmentStore.setApiUrl(config.apiUrl)
    equipmentStore.initialize()
    app.mount('#app')
  })
  .catch((error) => {
    console.error('Error:', error)
  })

EquipmentStore

import { ref, computed, watch } from 'vue'
import { defineStore, storeToRefs } from 'pinia'

export const useEquipmentStore = defineStore('EquipmentStore', () => {
    // State variables
    const apiUrl = ref('inject me')
    const equipmentList = ref([])

    const setApiUrl = (apiUrlValue) => {
        apiUrl.value = apiUrlValue
        return this;
    }

    // Fetch the equipment list when the component is created
    const fetchEquipmentList = async () => {
        console.log('fetchEquipmentList')
        const url = `${apiUrl.value}Equipment`

        try {
            const response = await fetch(url);

            if (!response.ok) {
                console.error('Failed to fetch equipment shortlist');
                return;
            }

            const result = await response.json();
            equipmentList.value = result.resources;
        } catch (error) {
            console.error('Error fetching equipment shortlist:', error);
        }
    }
 
    async function initialize() {
        Promise.all([fetchEquipmentList()])
        .then(() => {
            console.log('initialized');
        })
    }


    return {
        apiUrl,
        equipmentList,
        initialize,
        setApiUrl,
    }
})

Upvotes: 1

Views: 63

Answers (1)

Reyno
Reyno

Reputation: 6525

I've created a simple and stripped down version of your provided example. All it does is add an extra isLoading ref in your store that can be used to display a loading state. E.g:

const { createApp, ref } = Vue;
const { createPinia, defineStore } = Pinia;

// Store.
const useEquipmentStore = defineStore( 'EquipmentStore', () => {
  const isLoading = ref( false );
  const equipmentList = ref( [] );

  const fetchEquipmentList = async () => {
    isLoading.value = true;

    try {
      // Mock of equipment list fetch.
      await new Promise( resolve => setTimeout( resolve, 2000 ) );
      equipmentList.value = [ 'foo', 'bar', 'baz' ];
    }
    catch ( error ) {
      console.error( 'Error fetching equipment shortlist:', error );
    }
    finally {
      isLoading.value = false;
    }
  };

  async function initialize() {
    await fetchEquipmentList();
  }

  return {
    equipmentList,
    initialize,
    isLoading,
  };
} );

// App.
const app = createApp( {
  setup() {
    const store = useEquipmentStore();
    return { store };
  },
  mounted() {
    this.store.initialize();
  },
  template: `
    <div>
      <div v-if="store.isLoading">
        Fetching data...
      </div>

      <div v-else>
        Data received: {{ store.equipmentList }}
      </div>
    </div>`,
} );

const pinia = createPinia();

app.use( pinia );
app.mount( '#app' );
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.5.4/vue.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/index.iife.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pinia.iife.min.js"></script>

<div id="app"></div>

Alternatively you can use a status instead of isLoading to differentiate between muliple states (this is also done in Nuxt useFetch for example).

const { createApp, ref } = Vue;
const { createPinia, defineStore } = Pinia;

// Store.
const useEquipmentStore = defineStore( 'EquipmentStore', () => {
  const status = ref( 'idle' );
  const equipmentList = ref( [] );

  const fetchEquipmentList = async () => {
    status.value = 'loading';

    try {
      // Mock of equipment list fetch.
      await new Promise( resolve => setTimeout( resolve, 2000 ) );
      equipmentList.value = [ 'foo', 'bar', 'baz' ];
      
      // uncomment next line to simulate an error.
      // throw new Error( 'Failed retrieving equipment list.' );
      
      status.value = 'success';
    }
    catch ( error ) {
      status.value = 'error';
    }
  };

  async function initialize() {
    await fetchEquipmentList();
  }

  return {
    equipmentList,
    initialize,
    status,
  };
} );

// App.
const app = createApp( {
  setup() {
    const store = useEquipmentStore();
    return { store };
  },
  mounted() {
    this.store.initialize();
  },
  template: `
    <div>
      <p>Status: {{ store.status }}</p>
      
      <div v-if="store.status === 'loading'">
        Fetching data...
      </div>

      <div v-else-if="store.status === 'success'">
        Data received: {{ store.equipmentList }}
      </div>
      
      <div v-else-if="store.status === 'error'">
        <p>Something went wrong!</p>
      </div>
    </div>`,
} );

const pinia = createPinia();

app.use( pinia );
app.mount( '#app' );
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.5.4/vue.global.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/index.iife.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/pinia.iife.min.js"></script>

<div id="app"></div>

Upvotes: 2

Related Questions