Reputation: 7785
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
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