I'm building a web app using Nuxt v2.15 and #nuxtjs/i18n v7.2. I'm using Vuex for state management. In my global state, I want to create a getter that returns a value based on this.$i18n.locale.
What is the best way to access the current app context, which has $i18n attached to it, in my Vuex getter method?
nuxt.config.js
export default {
modules: [
"#nuxtjs/i18n", // https://i18n.nuxtjs.org/
],
i18n: {
locales: { /* configuration */ },
defaultLocale: "en",
detectBrowserLanguage: {
fallbackLocale: "en",
redirectOn: "root",
},
langDir: "~/locales/",
vueI18n: {
fallbackLocale: "en",
messages: { /* translation data */ },
},
},
};
store/index.js
export const state = () => ({
// Root module for Vuex store: https://nuxtjs.org/docs/directory-structure/store/
});
export const getters = {
loginOpts() {
const locale = this.$i18n.locale;
const uiLocales = [locale];
if (locale === "zh") {
uiLocales.push("zh-CN");
}
return { params: { ui_locales: uiLocales } };
},
};
components/login.vue
<template>
<div>
<v-btn v-if="$auth.loggedIn" #click="$auth.logout()">Sign Out</v-btn>
<v-btn v-else #click="$auth.loginWith('auth0', $store.getters.loginOpts)">Sign In</v-btn>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Login",
data() {
return {};
},
computed: {
...mapGetters(["loginOpts"]),
},
};
</script>
What I expect
I expect to be able to access this.$i18n from a Vuex getter, or to have some means of doing so.
What actually happens
In the getter method, this is undefined.
TypeError: Cannot read properties of undefined (reading '$i18n')
What I've tried
I see here that a getter is passed four arguments: state, getters, rootState, and rootGetters.
I've tried:
RTFM for Vuex State, Getters, and Nuxt i18n
accessing state.$i18n
adding $i18n: this.$i18n to the root state
I had a similar problem last week where I absolutely needed to use i18n inside a vuex getter.
I wouldn't recommend this as its probably not the most performant thing in the world but it worked for me and solved a huge problem while I was working on a government application.
components/login.vue
<template>
<div>
<v-btn v-if="$auth.loggedIn" #click="$auth.logout()">Sign Out</v-btn>
<v-btn v-else #click="$auth.loginWith('auth0', theLoginOpts)">Sign In</v-btn>
</div>
</template>
<script>
import { mapGetters } from "vuex";
export default {
name: "Login",
data() {
return {
theLoginOpts: $store.getters.loginOpts(this) // The secret sauce
};
},
computed: {
...mapGetters(["loginOpts"]),
},
};
</script>
store/index.js
export const state = () => ({
// Root module for Vuex store: https://nuxtjs.org/docs/directory-structure/store/
});
export const getters = {
loginOpts: (state) => (app) => { // App is the secret sauce from earlier
const locale = app.$i18n.locale;
const uiLocales = [locale];
if (locale === "zh") {
uiLocales.push("zh-CN");
}
return { params: { ui_locales: uiLocales } };
},
};
After digging through docs, experimenting, and discussing with the folks who were kind enough to offer answers here, it is clear that i18n is not directly accessible in a Vuex getter method, despite the fact that #nuxt/i18n registers a Vuex module called i18n and everything I've read about Vuex modules suggests this should be possible.
I did however come across the docs for #nuxt/i18n callbacks, which led me to create a small plugin that sets the value of locale and uiLocales in the global state using a mutation.
The end result looks like this:
nuxt.config.js
export default {
modules: [
"#nuxtjs/i18n", // https://i18n.nuxtjs.org/
],
plugins: [
"~/plugins/i18n.js",
],
i18n: {
locales: { /* configuration */ },
defaultLocale: "en",
detectBrowserLanguage: {
fallbackLocale: "en",
redirectOn: "root",
},
langDir: "~/locales/",
vueI18n: {
fallbackLocale: "en",
messages: { /* translation data */ },
},
},
};
plugins/i18n.js
export function findLocaleConfig (i18n, locale) {
return (
i18n.locales.find(({ iso, code }) => [iso, code].includes(locale)) || {}
);
}
export default function ({ app }) {
app.store.commit("localeChange", findLocaleConfig(app.i18n, app.i18n.locale));
app.i18n.onLanguageSwitched = (oldLocale, newLocale) => {
app.store.commit("localeChange", findLocaleConfig(app.i18n, newLocale));
};
}
store/index.js
export const state = () => ({
locale: null,
uiLocales: [],
});
export const mutations = {
localeChange(state, locale) {
state.locale = locale.code;
state.uiLocales = [locale.code, locale.iso];
},
};
components/login.vue
<template>
<div>
<v-btn v-if="$auth.loggedIn" #click="$auth.logout()">Sign Out</v-btn>
<v-btn v-else #click="$auth.loginWith('auth0', loginOpts)">Sign In</v-btn>
</div>
</template>
<script>
export default {
name: "Login",
data() {
return {};
},
computed: {
loginOpts() {
const uiLocales = this.$store.state.uiLocales;
return { params: { ui_locales: uiLocales } };
},
},
};
</script>
You can access a the locale in an Vuex action with: this.app.i18n.locale.
This cannot be accessed in a state or getter (a getter is a not a place for it anyway IMO).
PS: The above means that means that you can access this value anywhere you have access to the Nuxt context.
The simple way we did in our project is like below:
In component computed prop
teamsForSelector() {
return this.$store.getters['company/teamsForSelector'](this.$i18n);
}
and in state getters
teamsForSelector: (state) => ($i18n) => {
const teamsForSelector = state.teams.map((team) => {
return {
id: team.teamUid,
label: team.teamName,
};
});
teamsForSelector.unshift({
id: 'all-teams',
label: $i18n.t('key'),
});
return teamsForSelector;
}
I'm using the Vue controller to update the content on my SPA. While I can import views and controllers and display them accordingly, I'm having a hard time updating some variables defined in the main Vue instance. I also tried to define them in the main view but I still cannot update them.
Here's my Vue instance:
new Vue({
router,
render: h => h(App),
data: () => {
return {
showYesOrNo: false,
showMultiChoice: false
}
}
}).$mount('#app')
This is my main view:
<script type="text/javascript">
import ContactContainer from "./views/ContactContainer.vue"
export default {
name: "app",
components:{
ContactContainer
},
data: () => {
return { // Redefined the variables here to see if I could access them in some way
showYesOrNo: false,
showMultiChoice: false
}
}
}
</script>
<template>
<div id="app">
<div class="container">
<div id="main_container">
<transition name="fade"><router-view name="YesOrNo" v-show="!showMultiChoice"/></transition>
<transition name="fade"><router-view name="MultiChoice" v-show="!showYesOrNo"/></transition>
<transition name="fade"><ContactContainer name="Contact" v-show="!showMultiChoice && !showYesOrNo"></ContactContainer></transition>
</div>
</section>
</div>
</div>
</template>
Now this is how I try to access the ShowYesOrNo and ShowMultiChoice variables. First in the controller:
const routes = [
{
path: '/YesOrNo',
components: {
YesOrNo: YesOrNoVue,
MultiChoice: MultiChoiceContainer
},
beforeEnter: (to, from, next) => {
console.log(this); //undefined
console.log(this.$parent.showYesOrNo) //undefined
console.log(vm.showYesOrNo) //undefined
next();
}
}
]
Then in the YesOrNo vue:
<template>
<!-- Some HTML -->
</template>
<script>
export default {
data: function(){
return {
name: 'YesOrNoVue'
}
},
created: function(){
console.log(this.showYesOrNo); //undefined
console.log(vm.showYesOrNo); //undefined
}
}
</script>
I'm a little confused with the visibility of these variables so I'd like to know what's the best approach to change them when the router-view is updated (i.e. when the vue is created).
Thanks.
With some help from StackOverflow I got the following to run my loadData when the page loads and when the button is clicked.
However the text on the page is not updating. Something is wrong about my syntax with this.text = xhr.data
index.html:
<div id="app"></div>
app.js:
const Vue = window.Vue = require("vue");
Vue.prototype.$http = require("axios");
const App = require("./components/App.vue");
window.app = new Vue({
el: "#app",
render: h => h(App)
});
components/app.vue:
<template>
<div>
<h1>Test</h1>
<p>{{text}}</p>
<button #click="this.loadData">Reload</button>
</div>
</template>
<script>
export default {
mounted() {
this.loadData();
},
methods: {
loadData() {
this.$http.get("https://icanhazip.com")
// This fails
.then(xhr => this.text = xhr.data);
}
}
};
</script>
You must to define your text property in components data.
From Vue.js documentation:
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), Vue cannot detect property addition or deletion. Since Vue performs the getter/setter conversion process during instance initialization, a property must be present in the data object in order for Vue to convert it and make it reactive. For example:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
In your case your component should look like this:
<script>
export default {
created() {
this.loadData();
},
data() {
return {
text: '',
};
},
methods: {
loadData() {
this.$http.get("https://icanhazip.com")
// This fails
.then(xhr => this.text = xhr.data);
}
}
};
</script>
I am trying to pass a variable (here, externalVar) to a component, directly when initializing. But I can't find how to do it (probably not understanding documentation well :/ ). What is the correct way to do it?
The initialization :
var externalVar = "hello world"
const leftmenu = new Vue({
el: "#left-menu",
template: "<CLM/>",
components: {CLM},
variableToPass: externalVar
});
The component I am initializing here is defined like this (getting back variableToPass in data):
<template src="./template-l-m.html"></template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'leftmenu',
components: {
draggable
},
data () {
return {
jsonObject: this.variableToPass,
}
},
[ ... ]
</script>
But then , when I am trying to use this.jsonObject, it says that it's undefined. What am I doing wrong ?
If i understand you correctly you want to use props to pass data to child components
Dynamically bind a prop attribute on child component element using :variable="variableToPass"
var externalVar = "hello world"
const leftmenu = new Vue({
el: "#left-menu",
template: "<CLM :variable='variableToPass'/>",
components: {CLM},
data: {
variableToPass: externalVar
}
});
Define a props option in the child component
<template src="./template-l-m.html"></template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'leftmenu',
components: {
draggable
},
props: ['variable'],
data () {
return {
jsonObject: this.variable,
}
},
[ ... ]
</script>
Use data.
var externalVar = "hello world"
const leftmenu = new Vue({
el: "#left-menu",
template: "<CLM/>",
components: {CLM},
data: {
variableToPass: externalVar
}
});
That way you can access your variable like this this.$data.variableToPass
Use $options
in child component
mounted() {
console.log(this.$parent.$options.variableToPass) // hello world
this.jsonObject = this.$parent.$options.variableToPass
}
Just started to integrate vuex to my laravel application, going through with the sample counter application in the official vuex docs.
js/components/calculate.vue
<template>
<div>
<p>{{ count }}</p>
<p>
<button #click="increment">+</button>
<button #click="decrement">-</button>
</p>
</div>
</template>
<script >
import store from '../app.js'
export default {
computed: {
count () {
return store.state.count
}
},
methods: {
increment () {
store.commit('increment')
},
decrement () {
store.commit('decrement')
}
}
}
</script>
js/app.js
const calculate = Vue.component('calculate', require('./components/calculate.vue'));
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
});
const app = new Vue({
el: '#app'
});
Using webpack build
I got store.state.count not defined error in this line return store.count.
If you are creating your store in app.js file, (since you haven't mentioned) you need to add
const calculate = Vue.component('calculate', require('./components/calculate.vue'));
Vue.use(Vuex) // this line
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment: state => state.count++,
decrement: state => state.count--
}
});
const app = new Vue({
el: '#app'
});
This goes without saying that you need to import Vuex in this file.
after which you need to modify, this part of your code:
const app = new Vue({
el: '#app'
});
to
const app = new Vue({
el: '#app',
store // if you're vue-cli/babel else use store: store
});
And in js/components/calculate.vue
<template>
<div>
<p>{{ count }}</p>
<p>
<button #click="increment">+</button>
<button #click="decrement">-</button>
</p>
</div>
</template>
<script >
// import store from '../app.js' this import is not required
export default {
computed: {
count () {
return this.$store.state.count
}
},
methods: {
increment () {
this.$store.commit('increment')
},
decrement () {
this.$store.commit('decrement')
}
}
}
</script>
The import is not required as the store is supposed to be accessible to each component of the vue instance, to access it, you just need to have access to $store property of the vue instance which in your component is this (as long as the context doesn't change by means of a callback or something similar.)