Even though I have an action that changes the 'pending' state depending on if the data was already fetched (which makes it already reactive), when I use it inside of the component, reactivity doesn't work and this is mentioned here in the official Pinia docs: https://pinia.vuejs.org/ssr/#Using-the-store-outside-of-setup-. Then I tried to make a 'pending' state reactive by using storeToRefs, but I keep getting this error:
Uncaught Error: [🍍]: "getActivePinia()" was called but there was no active Pinia. Did you forget to install pinia?
const pinia = createPinia()
app.use(pinia)
This will fail in production.
Here is my Pinia store:
import { defineStore, storeToRefs } from 'pinia';
export const useStoreRecipes = defineStore('storeRecipes', {
state: () => {
return {
data: [],
pending: false,
};
},
actions: {
async loadRecipes() {
try {
this.pending = true;
const res = await fetch('/api/recipe');
if (res.ok) {
const data = await res.json();
this.data = data;
} else {
console.error('Error: ', res.status, res.statusText);
}
} catch (err) {
console.error(err);
} finally {
this.pending = false;
}
},
},
});
const store = useStoreRecipes();
export const { pending } = storeToRefs(store);
My main.js:
import '@/assets/scss/main.scss';
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import router from '@/router';
import App from './App.vue';
const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.use(router);
app.mount('#app');
and then I'm using 'pending' in one of my components.
<template>
<Swiper
class="swiper"
:breakpoints="swiperOptions.breakpoints"
:pagination="{
clickable: true,
}"
:loop="true"
:modules="swiperOptions.modules">
<template v-for="recipe in storeRecipes.data" :key="recipe.id">
<SwiperSlide class="swiper__slide">
<ItemCard :data="recipe" :pending="storeRecipes.pending" />
</SwiperSlide>
<div class="swiper-custom-pagination"></div>
</template>
</Swiper>
</template>
<script setup>
import { onMounted } from 'vue';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Pagination } from 'swiper/modules';
import 'swiper/css/free-mode';
import 'swiper/css/pagination';
import 'swiper/css';
import { useStoreRecipes } from '@/stores/recipes/storeRecipes.js';
import ItemCard from '@/components/ItemCard.vue';
const storeRecipes = useStoreRecipes();
onMounted(async () => {
await storeRecipes.loadRecipes();
});
</script>
How to fix it?
The error is gone now, but the 'pending' still acts like it's not reactive. Here, where I pass the props to the child component on skeleton load. For some reason, the skeleton loads infinitely :
<template>
<div class="card">
<div class="card__item">
<ItemCardSkeleton v-if="!pending" />
<template v-else>
<img
class="card__image"
:src="getSrc('.jpg')"
:alt="data.alt"
width="15.625rem" />
<div class="card__content">
<h2 class="card__title">{{ data.title }}</h2>
<p class="card__text">{{ data.text }}</p>
<router-link class="card__link" :to="{ name: 'Home' }"
>View more</router-link
>
</div>
</template>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import ItemCardSkeleton from '@/components/SkeletonLoaders/ItemCardSkeleton.vue';
const props = defineProps(['data', 'pending']);
const isLoaded = ref(false);
const getSrc = ext => {
return new URL(
`../assets/images/recipe/${props.data.image}${ext}`,
import.meta.url
).href;
};
onMounted(() => {
const img = new Image(getSrc('.jpg'));
img.onload = () => {
isLoaded.value = true;
};
img.src = getSrc('.jpg');
});
</script>
This code needs to be removed from your store
const store = useStoreRecipes();
export const { pending } = storeToRefs(store);
An interesting idea, but it doesn't work. The pinia instance isn't active yet. Deconstructing refs from your store should really be done in your component code:
<script setup>
import { useStoreRecipes } from '@/stores/recipes/storeRecipes.js';
import { storeToRefs } from 'pinia';
const storeRecipes = useStoreRecipes();
const { pending } = storeToRefs(store);
</script>