Access store vuex in quasar for meta - vuex

I'm using quasar in SSR mode and I would like to implement meta data. Quasar has a plugin for meta data and I wanted to use it.
For now my project is quite simple, I have a page product (/product/:id) which display product information.
In the page Product, I use prefetch to fetch Data from a remote API base on the param id.
<template>
<q-page padding>
<div v-if="product">
<h1>{{product.name}}</h1>
<span v-html="product.description"/>
</div>
</q-page>
</template>
<script>
import store from '../store'
export default {
name: 'Product',
async preFetch ({ store, currentRoute }) {
let id = currentRoute.params.id
if (typeof store.getters['product/getProduct'](id) === 'undefined') {
let promise = store.dispatch('product/fetchProduct', id)
await promise.then({})
}
},
computed: {
product () {
return this.$store.getters['product/getProduct'](this.$route.params.id)
}
}
}
</script>
So far everything work find. I have my data setted in the store before rendering. But now I wanted to had a meta title. But in meta how can I have acces to the store and route.params.id.
<script>
export default {
name 'Product',
...
meta: {
title: store.getters['product/getProductMetaTitle'](route.params.id)
}
}
</script>

Related

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

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?

Vue3 - after provide/inject object inside component is loosing data

I'm having small issue with provide/inject in my project.
In App.vue, I'm pulling data from DB and pushing it into object. With console log I checked and all data it's there.
<template>
<router-view />
</template>
<script>
export default {
provide() {
return {
user: this.user,
};
},
data() {
return {
user: '',
};
},
methods: {
///pulling data from DB
func() {
fetch("url")
.then((response) => {
if (response.ok) {
return response.json();
}
})
.then((data) => {
const user = [];
for (const id in data) {
user.push({
id: data[id].user_id,
firstName: data[id].user_firstname,
lastName: data[id].user_lastname,
email: data[id].user_email,
phone: data[id].user_phone,
address1: data[id].user_address_1,
address2: data[id].user_address_2,
address3: data[id].user_address_3,
address4: data[id].user_address_4,
group: data[id].user_group,
});
}
this.user = user;
})
.catch((error) => {
console.log(error);
});
},
},
created() {
this.func();
},
};
</script>
Console log of object user App.vue
Object { id: "3", firstName: "test", lastName: "test", … }
Next I'm injecting it into component. Object inside component exists, but empty - all data cease to exist.
<script>
export default {
inject: ["user"],
};
</script>
console log of object user in component
<empty string>
While in App.vue data is still there, in any components object appears to be empty, but it is there. Any idea why?
Thanks for help.
In short, this happens because you are reassigning user rather than changing user.
Let's say you have a Child component that consumes your inject data and renders the users in a list:
<template>
<div> Child </div>
<ul>
<li v-for="user in users" :key="user.id"> {{user.name}} </li>
</ul>
</template>
<script>
import {inject} from "vue";
export default {
name: "Child",
setup() {
const users = inject("users");
return {users};
}
}
</script>
To provide the users from parent component, all you need to ensure is that users itself is a reactive object, and you keep changing it from the parent rather than reassigning it.
I am going to use the composition api to illustrate what I mean. Compared to options api, everything in composition api is just plain javascript hence there is a lot less behind-the-scene magic. At the end I will tell you how options api is related to the composition api.
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {reactive, provide, toRefs} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const data = reactive({users: ""});
const generateUsers = () => {
// notice here you are REASSIGNING the users
data.users = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(data.users);
}
// this way of provide will NOT work
provide("users", data.users);
// this way works because of toRefs
const {users} = toRefs(data);
provide("users", users);
return {generateUsers};
}
}
</script>
A few things to note:
the data options in the options api is exactly the same as const data = reactive({users: ""}). Vue will run your data() method, from where you have to return a plain object. And then Vue will automatically call reactive to add reactivity to it.
provide, on the other hand, is not doing any magic - neither in options api, nor in the composition api. It just passes whatever it is given to the consuming component without any massaging.
the reason provide("users", data.users) does not work as you would expect is that the way you populate the users is not a change to the same data.users object (which actually is reactive), but a reassign all together.
the reason toRefs works is because toRefs links to the original parent.
With this understanding in mind, to fix your original code, you just need to ensure you change, instead of reassigning, the users. The simplest way is to define user as an array and push into it when you load data. (in contrast to defining it initially as a string and reassigning it later)
P.S. what also works in composition api, and is a lot simpler, is to:
<template>
<button #click=generateUsers>
Generate Users
</button>
<Child/>
</template>
<script>
import {ref, provide} from "vue";
import Child from "./Child.vue";
export default {
name: "App",
components: {
Child
},
setup() {
const users = ref();
const generateUsers = () => {
// notice here you are not reassigning the users
// but CHANGING its value
users.value = [
{id: 1, name: "Alice"}, {id: 2, name: "Bob"}
];
console.log(users.value);
}
provide("users", users);
return {generateUsers};
}
}
</script>

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

How can I pass a variable value from a "page" to "layout" in Nuxt JS?

I'm a beginner in VUE and donnow this one is the correct syntax. I need the variable {{name}} to be set from a page. Which means I need to change the value of the variable page to page. How can I achieve that? Help me guys.
My "Layout" Code is like below -
<template>
<div class="login-page">
<div class="col1">{{ name }}</div>
<div class="col2">
<div class="content-box">
<nuxt />
</div>
</div>
</div>
</template>
<script>
export default {
props: ['name']
}
</script>
And my "Page" code is following -
<template>
<div>Welcome</div>
</template>
<script>
export default {
layout: 'login',
data: function() {
return {
name: 'Victor'
}
}
}
</script>
this can be achieved by using the vuex module. The layout have access to the vuex store, so once a page is open, you can call a mutation to set the page name and listen the name state in the layout component.
First the Vuex module, we can add a module by creating a file in the store folder,
in this case we are creating the page module:
// page.js file in the store folder
const state = {
name: ''
}
const mutations = {
setName(state, name) {
state.name = name
}
}
const getters = {
getName: (state) => state.name
}
export default {
state,
mutations,
getters
}
Now we can use the setPageName mutation to set the pageName value once a page reach the created hook (also can be the mounted hook):
// Page.vue page
<template>
<div>Welcome</div>
</template>
<script>
export default {
layout: 'login',
created() {
this.$store.commit('page/setName', 'Hello')
},
}
</script>
And in the layout component we have the computed property pageName (or name if we want):
<template>
<div class="login-page">
<div class="col1">{{ name }}</div>
<div class="col2">
<div class="content-box">
<nuxt />
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
name() {
return this.$store.getters['page/getName']
}
}
}
</script>
And it's done!
Answer to your question in the commets:
The idea behind modules is keep the related information to some functionality in one place. I.e Let's say you want to have name, title and subtitle for each page, so the page module state variable will be:
const state = { name: '', title: '', subtitle: ''}
Each variable can be updated with a mutation, declaring:
const mutations = {
setName(state, name) {
state.name = name
},
setPageTitle(state, title) {
state.title = title
},
setPageSubtitle(state, subtitle) {
state.subtitle = subtitle
},
}
And their values can be updated from any page with:
this.$store.commit('page/setPageTitle', 'A page title')
The same if you want to read the value:
computed: {
title() {
// you can get the variable state without a getter
// ['page'] is the module name, nuxt create the module name
// using the file name page.js
return this.$store.state['page'].title
}
}
The getters are good for format or filter information.
A new module can be added anytime if required, the idea behind vuex and the modules is to have a place with the information that is required in many places through the application, in one place. I.e. the application theme information, if the user select the light or dark theme, maybe the colors can be changed. You can read more about vuex with nuxt here: https://nuxtjs.org/guide/vuex-store/ and https://vuex.vuejs.org/

Where do I store shareable data in vuejs?

I am building an app with various pages, and when users goes to /orgs I have a template that I require
// routes.js
...
import Orgs from './components/Orgs.vue';
...
{
path: '/orgs',
component: Orgs,
meta: { requiresAuth: true }
},
from here I have a simple template in Orgs.vue that looks like:
<template lang="html">
<div> {{orgs}} </div>
</template>
<script>
export default {
data(){
return {
orgs: [];
}
},
created() {
//use axios to fetch orgs
this.orgs = response.data.orgs;
}
}
</script>
The problem is that if I want to show list of organizations in other pages, I am bound to duplicate the same code for other pages as well, but I am trying to find a solution that would call return organizations so I can use that in multiple page?
What is the solution for this?
To make data available across the application use Vuex.
It is state management library which stores all the application data in a single source tree.
If you don't want to you vuex for the above issue, you can try mixins.
Mixins are best way to share functionality.
For the above case you can try a mixin like this.
organisation.mixin.js
const OrganisationMixin = Vue.mixin({
data: function () {
return { orgs: [] }
},
methods: {
fetchOrgs: function() {
// api to fetch orgs
this.orgs = result_from_api
}
}
mounted: function() {
this.fetchOrgs()
}
});
export default OrganisationMixin
Now let's use the mixin we just created.
In whatever_name_component.vue:
<template lang="html">
<div> {{orgs}} </div>
</template>
<script>
import OrganisationMixin from 'path_to_organisation.mixin.js'
export default {
mixins: [OrganisationMixin]
data(){
return { orgs: [] }
},
mounted() {
console.log(this.orgs) //provided by mixin` and value is equal to api response from mixin.
}
}
</script>