The data from Pinia store is not reactive in Nuxt 3 when switching language - api

I'm just starting with Nuxt and the answer could be obvious, but I'm hoping to get support from you.
I've got a 2 language website, built with Nuxt 3 that uses Nuxt I18n for internationalization, which retrieves data from an API (a strapi headless cms). I've managed to set up a Pinia store in order to not overuse the API, which looks like this:
// /stores/store.js
import { defineStore } from "pinia";
import { useFetch } from "#app";
export const useStore = defineStore("store", {
state: () => ({
data: {
en: [],
ru: []
}
}),
actions: {
async fetchData() {
let resEn = await useFetch('strapi-url.com/api/data', {
params: {
locale: 'en'
}
});
if (resEn.error.value) {
throw createError({
statusCode: resEn.error.value.statusCode,
statusMessage: resEn.error.value.statusMessage
});
}
this.data.en = resEn.data;
let resFr = await useFetch('strapi-url.com/api/data', {
params: {
locale: 'fr'
}
});
if (resFr.error.value) {
throw createError({
statusCode: resFr.error.value.statusCode,
statusMessage: resFr.error.value.statusMessage
});
}
this.data.fr = resFr.data;
}
}
});
And to make the data available when app loads I've setup the app.vue file:
<script setup>
import { useStore } from "~/stores/store";
const store = usetStore();
await store.fetchData();
</script>
<template>
<div>
<Header/>
<NuxtPage/>
<Footer/>
</div>
</template>
and then in a component (ex: Header.vue) I'm getting the data from the store an render it:
<script setup>
import { useStore } from "~/stores/NewsletterStore";
import { storeToRefs } from "pinia";
const { locale } = useI18n();
const store = useStore();
const { data } = storeToRefs(store);
const title = data[locale].title;
</script>
<template>
<div>
{{ title }}
</div>
</template>
The problem is that when the language changes, by a locale switcher, the data isn't refreshed, even if the locale changes too.
I would like to know if there's any way to make it reactive, based on the selected locale.
Thanks & looking forward.
I've tried to setup a pinia store using nuxt 3 web app that has 2 languages controlled by Nuxt I18n module that consumes data from an strapi backend API, but the data rendered isn't reactive when changing locale. I expect to know how to make this data be reactive, when language changes?

Related

Composition API | Vue route params not being watched

I have pretty much the same example from the docs of watch and vue router for composition API. But console.log is never getting triggered even though the route.params.gameId is correctly displayed in template
<script setup>
import { ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'
const store = useStore()
const route = useRoute()
watch(
() => route.params.gameId,
async newId => {
console.log("watch"+newId)
}
)
</script>
<template>
<div>
{{route.params.gameId}}
</div>
</template>
What am I doing wrong? I also tried making watch function non async but it didn't change anything and later on I will need it for api fetching so it should be async.
You should add immediate: true option to your watch :
watch(
() => route.params.gameId,
async newId => {
console.log("watch"+newId)
},
{
immediate: true
}
)
Because the watch doesn't run at the first rendering while the params is changes only one time at the page load, So the watch misses that change.

Why does calling "useUserStore(store)" in a Quasar boot file breaks the Pinia Persistedstate Plugin?

I'm trying to use the Pinia Persisted State Plugin with Pinia in my Quasar app (Vue 3 / TypeScript).
Out of the box everything works fine.
But when using a Quasar boot file the persisted state stops working. Refreshing the page wipes all the new values away.
I don't know why the boot file breaks the persisted state plugin, but I have narrowed the culprit down to a single line...
This is how I am using Pinia with Quasar and adding the plugin:
src/store/index.ts
/* eslint-disable #typescript-eslint/no-unused-vars */
import { store } from 'quasar/wrappers';
import { createPinia, Pinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
declare module '#quasar/app' {
interface BootFileParams<TState> {
store: Pinia;
}
interface PreFetchOptions<TState> {
store: Pinia;
}
}
declare module '#vue/runtime-core' {
interface ComponentCustomProperties {
$store: import('pinia').Pinia;
}
}
export default store(function (_) {
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate); // Pinia Plugin added here
return pinia;
});
And this is what my Pinia store looks like:
src/store/user.ts
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => {
return {
user: {
firstName: 'Mary',
},
};
},
persist: true, // Note that we are using a persisted state here
actions: {
setFirstName(firstName: string) {
this.user.firstName = firstName;
console.log('Name set to Pinia store: ', this.user.firstName);
},
getFirstName() {
if (!this.user.firstName) {
console.log('No name found in store. Setting "John" to Pinia store.');
this.user.firstName = 'John';
return this.user.firstName;
} else {
console.log('Name fetched from Pinia store: ', this.user.firstName);
return this.user.firstName;
}
},
},
});
Here is an example front-end page for fetching and setting the firstName:
src/pages/index.vue
<template>
<div>{{ firstName }}</div>
<q-form #submit="handleFirstNameSubmit">
<p>Change First Name</p>
<q-input v-model="firstNameInput" filled outline />
<q-btn label="Submit Name to Pinia Store" type="submit" />
</q-form>
<q-btn #click="handleFirstNameFetch" label="Fetch Name from Pinia Store" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useUserStore } from 'src/store/user';
const userStore = useUserStore();
const firstName = ref<string>();
const firstNameInput = ref<string>();
const handleFirstNameSubmit = () => {
if (firstNameInput.value) {
userStore.setFirstName(firstNameInput.value);
}
};
const handleFirstNameFetch = () => {
firstName.value = userStore.getFirstName();
};
</script>
Up to this point everything works fine.
I can set firstName to the Pinia store, refresh the page, and the new name is still in Pinia.
But when using const userStore = useUserStore(store) inside a boot file like the example below, the persisted state stops working:
src/boot/auth.ts
import { boot } from 'quasar/wrappers';
import { useUserStore } from 'src/store/user';
export default boot(({ store }) => {
const userStore = useUserStore(store);
// Do some other authentication stuff, setting initial user store values etc, below here...
});
Any idea what's going on? And how to fix it?
I think this plugin is much cleaner than using the alternate LocalStorage persisted state solution so I would love to get it working with Quasar.
everyone after a lot of research I found the answer to this issue,
you must pass index.ts/js for const like below:
this is worked for me in quasar.:)
<script lang="ts" setup>
import store from '../stores/index';
import { useCounterStore } from '../stores/counter';
const counterStore = useCounterStore(store());
counterStore.increment();
console.log(counterStore.count);
</script>
A common use case for Quasar applications is to run code before the root Vue app instance is instantiated.
If the app is not instantiated then the pinia plugin hasn't been installed yet. See: https://github.com/vuejs/pinia/discussions/723#discussioncomment-2110660

WebGL Earth or globe.gl in VUE.JS

Can I use "WebGL Earth" or "globe.gl" in vue.js? I search a lot but what I found was that there is "react-globe.gl" for react developers, but can't find the same for vue.
If I can use any of them in vue, how can I import and initialize it?
I am currently am using globe.gl with vue 3, got it running like this.
Can also checkout a template repo I have https://github.com/GBerghoff/Globe.gl-with-Vue-3
<template>
<div ref="globeDiv"></div>
</template>
<script>
import Globe from "globe.gl";
import { ref, onMounted } from "vue";
export default {
setup() {
const globeDiv = ref(null);
onMounted(() => {
const myGlobe = Globe();
myGlobe(globeDiv.value).globeImageUrl(
"//unpkg.com/three-globe/example/img/earth-night.jpg"
);
});
return {
globeDiv,
};
},
};
</script>

Vuex is resetting already set states

Have started to play around with Vuex and am a bit confused.
It triggers the action GET_RECRUITERS everytime I load the component company.vue thus also making an api-call.
For example if I open company.vue => navigate to the user/edit.vue with vue-router and them go back it will call the action/api again (The recruiters are saved in the store accordinly to Vue-dev-tools).
Please correct me if I'm wrong - It should not trigger the action/api and thus resetting the state if I go back to the page again, correct? Or have I missunderstood the intent of Vuex?
company.vue
<template>
<card>
<select>
<option v-for="recruiter in recruiters"
:value="recruiter.id">
{{ recruiter.name }}
</option>
</select>
</card>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
middleware: 'auth',
mounted() {
this.$store.dispatch("company/GET_RECRUITERS")
},
computed: mapGetters({
recruiters: 'company/recruiters'
}),
}
</script>
company.js
import axios from 'axios'
// state
export const state = {
recruiters: [],
}
// getters
export const getters = {
recruiters: state => {
return state.recruiters
}
}
// actions
export const actions = {
GET_RECRUITERS(context) {
axios.get("api/recruiters")
.then((response) => {
console.log('API Action GET_RECRUITERS')
context.commit("GET_RECRUITERS", response.data.data)
})
.catch(() => { console.log("Error........") })
}
}
// mutations
export const mutations = {
GET_RECRUITERS(state, data) {
return state.recruiters = data
}
}
Thanks!
That's expected behavior, because a page component is created/mounted again each time you route back to it unless you cache it. Here are a few design patterns for this:
Load the data in App.vue which only runs once.
Or, check that the data isn't already loaded before making the API call:
// Testing that your `recruiters` getter has no length before loading data
mounted() {
if(!this.recruiters.length) {
this.$store.dispatch("company/GET_RECRUITERS");
}
}
Or, cache the page component so it's not recreated each time you route away and back. Do this by using the <keep-alive> component to wrap the <router-view>:
<keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>

Call nuxt/axios module from external js/ts file

I am new to vue and trying to build my first vue app using nuxtjs. My problem right now has to do with architecture and folder structure.
In my other non-vue apps I always have a "services" directory where I keep all my code that makes http requests.
example under my services folder I will have a auth.ts file that contains code that posts login credentials to my API. This file/class returns a promise which I access from within my store.
I am trying to do this with vue using nuxtjs but I realised I am unable to access the axios module from anywhere aside my .vue file.
This is an example of how my code is now:
<template>
...
</template>
<script lang="ts">
import Vue from 'vue'
import ActionBar from '../../components/ActionBar.vue'
export default Vue.extend({
components: { ActionBar },
data() {
return {
example: ''
},
methods: {},
mounted() {
this.$axios.$get('/examples').then((res) => {
this.examples = res.data;
})
}
})
</script>
<style>
...
</style>
I would like to move the axios calls to their own files in my services folder. How do I do this?
what you can do is create a file inside the ./store folder, let's imagine, ./store/products.js, that will create a products store, inside, simple getters, mutations and actions:
export const state = () => ({
products: [],
fetchingProducts: false,
})
export const getters = {
getAllProducts(state) {
return state.products
},
hasProducts(state) {
return state.products.length > 0
},
isFetchingProducts(state) {
return state.fetchingProducts
},
}
export const mutations = {
setInitialData(state, products) {
state.products = products
},
setLoadingProducts(state, isLoading) {
state.fetchingProducts = isLoading
},
}
export const actions = {
async fetchProducts(context, payload) {
context.commit('setLoadingProducts', true)
const url = `/api/example/${payload.something}`
const res = await this.$axios.get(url)
context.commit('setInitialData', res.data)
context.commit('setLoadingProducts', false)
},
}
then in your .vue file, you can now use the store as:
<template>
<div>
<div v-if="isFetchingProducts"> loading... </div>
<div v-else-if="!hasProducts">no products found</div>
<div v-else>
<ul>
<li v-for="product in allProducts" :key="product.id">
{{ product.name }}
</li>
</ul>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
export default {
data () {
return {
products: []
}
},
methods: {
...mapGetters({
isFetchingProducts: 'products/isFetchingProducts',
allProducts: 'products/getAllProducts',
hasProducts: 'products/hasProducts',
})
},
mounted() {
this.$store.dispatch('products/fetchProducts', {})
},
}
</script>
<style>
...
</style>
remember that:
to call a store action, you should use $store.dispatch()
to call a mutation, you should use $store.commit()
to call a getter, you should use $store.getter()
you can also use the Vuex helper mapGetters, mapActions and even mapMutations
You might also know that you can leverage the Plugins in Nuxt, that article has demo code as well so you can follow up really quick