allanberry
allanberry

Reputation: 7785

Vue3: data from Pinia store not arriving

I can't pull Pinia data after page reload using the Composition API.

So, I'm using Vue to digest some fetched JSON. I'm new to the Composition API. But reading the docs I can't see what's missing here.

In this example, there are two items: "Template" and "Setup" pulling from the same Pinia store, originally loaded via an async call in App.vue. "Template" loads directly from a call in the template, and "Setup" attempts to access the data via a Component API ref().

When initially loading the app, both load identically and correctly. But upon a page reload, or navigation away and back to the page, only "Template" remains. If I look into the Dev tools, the store is fully stocked and functional.

Presumably "Setup" disappears because of a race condition where the async data populates before the setup function can grab it. But this seems weird, because the data (eventually?) exists, and AFAICT everything is reactive. The route/path and the template don't seem to be the problem.

I've tried using various lifecycle hooks, but I'm at a loss. What gives?

<script setup>
import { ref } from 'vue'
import { useProfileStore } from '@/stores/profile'
const store = useProfileStore()

import { useRoute } from 'vue-router'
const route = useRoute()

const profile = ref()
profile.value = store.getProfileBySlug(route.params.slug)
</script>

<template>
  <div class="record">
    <ul>
      <li><h2>Template</h2><p>{{ store.getProfileBySlug(route.params.slug) }}</p></li>
      <li><h2>Setup</h2><p>{{ profile }}</p></li>
    </ul>
  </div>
</template>

My store code looks like this:

// ... means excised for brevity

import { defineStore } from 'pinia'

export const useProfileStore = defineStore('profile_store', {  
  state: () => ({
    profiles: [],
    // ... 
  }),
  getters: {
    profilesSorted: (state) => {
      // ... sorting code
    },
    getProfileBySlug: (state) => {
      return (slug) => state.profiles.find((profile) => profile.slug === slug)
    },
  },
  actions: {
    async initData() {
      // each model can either have a custom set of url options (profiles), or default
      const url_options = {
        perPage: 100,
        fields: [
          'slug',
          // ... other fields
        ]
      }
      // transform structure default
      const structure = (profile) => {
        return {
          slug: profile.slug,
          // ... other fields
        }
      }

      // data transformation reference from API to local use
      const models = {
        profiles: {
          main_url: '...',
          local_url: 'http://localhost:5173/profiles.json',
          url_options: {
            perPage: 100,
            fields: [
              'slug',
              // ... other fields
            ]
          },
          structure: (profile) => {
            return {
              slug: profile.slug,
              // ... other fields
            }
          }
        },
        // ...
      }

      async function fetchAndTransform(model) {
        const use_live_data = false
        const { perPage = 100, fields = [] } = model.url_options
        let page = 1
        let items = [] // Initialized to hold the fetched and transformed items
        let hasMore = true

        while (hasMore) {
          const url_obj = new URL(use_live_data ? model.main_url : model.local_url)
          const params = url_obj.searchParams

          // params, not necessary for local (static) data
          if (use_live_data) {
            // Set 'per_page' and 'page' parameters
            params.set('per_page', perPage)
            params.set('page', page)

            // Concatenate fields with commas and set as a single '_fields' parameter
            if (fields.length > 0) {
              params.set('_fields', fields.join(','))
            }
          }

          // Fetch and transform data
          try {
            const response = await fetch(url_obj)
            if (!response.ok) {
              throw new Error(`Failed to fetch page: ${page}`)
            }

            // Parse response
            const data = await response.json()

            // Transform each item
            items = items.concat(data.map((item) => model.structure(item)))

            // Decide whether to continue fetching
            if (!use_live_data || data.length < perPage) {
              hasMore = false
            }

            page++
          } catch (error) {
            console.error(error)
            break // Exit the loop on error
          }
        }

        return items
      }
      this.profiles = await fetchAndTransform(models.profiles)
      // ... additional data types
    },
  }
})

Upvotes: 0

Views: 504

Answers (1)

allanberry
allanberry

Reputation: 7785

I think I solved my problem, although I'm not exactly sure of the culprit.

reformatting my setup function helped:

<script setup>
import { computed } from 'vue'
import { useProfileStore } from '@/stores/profile'
import { useRoute } from 'vue-router'

const route = useRoute()
const store = useProfileStore()

// const slug = computed(() => route.params.slug)
const profile = computed(() => store.getProfileBySlug(route.params.slug))
</script>

But I think it had to do with a race condition between vue-router and the store. Playing around with import order may have helped.

Main.js:

import { createApp } from 'vue'
import { createPinia } from 'pinia'

import App from './App.vue'
import router from './router'

const app = createApp(App)
const store = createPinia()

app.use(router)
app.use(store)

app.mount('#app')

App.vue:

<script setup>
import { useProfileStore } from '@/stores/profile'
import { RouterView } from 'vue-router'


const store = useProfileStore()
store.initData()


import AppNav from './components/AppNav.vue'
</script>

In case it helps anyone else someday

Upvotes: 0

Related Questions