vue-i18n doesn't update locale after integrating vuex - vue.js

I'm still new to Vue, but I feel like I'm almost getting the hang of it.. I managed to create an app that could translate to different languages where the content was loaded from LANG.json files.. the problem was that whenever I changed to a new view then it would turn back to the original translation..
So I tried to integrate Vuex to my application, but I can't seem to get it to work..
I believe this is all the relevant code:
src/i18n/index.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import store from './../store'
Vue.use(VueI18n)
export default new VueI18n({
locale: store.state.Language,
messages: {
'en': require('./en.json'),
'cn': require('./cn.json')
}
})
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = {
changeLanguage: ({ commit }, data) => commit('changeLanguage', data)
}
const mutations = {
changeLanguage (state, data) {
this.state.Language = data
}
}
const getters = {
getLanguage: state => state.Language
}
const state = {
Language: 'en'
}
export default new Vuex.Store({
state,
getters,
actions,
mutations
})
src/main.js
[...]
import store from './store'
import i18n from './i18n'
[...]
new Vue({
el: '#app',
router,
store,
i18n,
render: h => h(App)
})
src/components/randomfile.js
<template>
[...]
<button v-on:click="en">English</button>
<button v-on:click="cn">Chinese</button>
</template>
<script>
export default {
name: 'navFooter',
data () {
return {
}
},
methods: {
en: function () {
console.log('change lang')
this.$store.dispatch('changeLanguage', 'en')
// this.$i18n.locale = 'en'
},
cn: function () {
this.$store.dispatch('changeLanguage', 'cn')
// this.$i18n.locale = 'cn'
}
}
}
</script>
I'm guessing the problem is either related to this line: locale: store.state.Language, or because I'm doing something wrong with the dispatch, because in one of my views I write {{$store.state.Language}}, which renders to en on page load, but disappears after I click the buttons that call the methods for dispatching.
I call the translations with {{ $t('views.home.title') }} but I'm pretty sure that's still the way they should be called after integrating vuex (store), and the translations do appear as they should, they just don't update after clicking the buttons that dispatch changeLanguage.
Any help would be much appreciated
Edit: Actually, it seems the way I translated did make a difference.. after changing it to {{ $t('views.home.title', $store.state.Language) }} (maybe this should be this.$store.state.Language?)then the translations started working again.. but the translation state is still not persistent (meaning if I refresh the page or open a new URL, then it will change back to the English translations...
Any ideas why that is?

Vuex store is not persisted. Reloading the page resets it.
To persist the locale, you should store it in a local storage (like IndexedDB) or a backend server.
Another way (not persisting) is to embed it in URL like /:locale/page. Then you can get the locale from the router params.
$t('views.home.title') uses the locale from this.$i18n.locale and you don't update this.$i18n.locale, then $t('views.home.title') shows the translation with the initial locale, 'en'.
$t('views.home.title', $store.state.Language) works because you specify the locale by yourself, not using this.$i18n.locale.

Related

vuejs - template, store, this.$store is undefined

I have a problem retreiving the vuex store in a vuejs component.
My architecture is pretty straight-forward. I have a store with two modules.
main.js
new Vue({
el: '#app',
store,
router,
template: '<App/>',
components: {
App
},
Events.vue - Here I use my custom component UserDropdown in a syncfusion component, but I dont think that's relevant. The first registers the UserDropdown, the second fragment will be called when you click on a cell and returns my custom component:
...
components: {
UserDropdown
},
...
editTemplate: function () {
return {template: UserDropdown}
},
...
UserDropdown.vue - here I'd like to use the store, but the result is: "this.$store is undefined". Access to the store from within other components like Events.vue works just fine.
computed: {
users: function () {
return this.$store.state.usersState.users;
}
store.js
import Vue from 'vue';
import Vuex from 'vuex';
import projectsState from './modules/projectsStore'
import usersState from './modules/usersStore'
import createLogger from 'vuex/dist/logger'
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== 'production';
export const store = new Vuex.Store({
modules: {
projectsState,
usersState
},
strict: debug,
plugins: debug ? [createLogger()] : []
});
Why doesnt that work? Is the problem related to the "template: UserDropdown"? I thought every component should be able to access the store...
As it looks, one has to import the store once again in the UserDropdown.vue component. That doesn't make any sense to me, since I imported the store in the new Vue instance as shown above.
Here the code fragments to be added in the UserDropdown.vue
...
import {store} from "../store/store";
...
export default {
store,
name: 'UserDropdown',
...
...mapGetters({users: 'usersState/AllUsers'})
...

How to use vuex action in function

I'm new to Vue, so it's likely I misunderstand something. I want to call a vuex action inside a local function in App.vue like so:
<template>
<div id="app">
<button #click="runFunction(1)">Test</button>
</div>
</template>
<script>
import { mapActions } from 'vuex'
export default{
data() { return { } },
methods: {
...mapActions(['doAction']),
buttonClicked: (input) => { runFunction(input) }
}
}
function runFunction(input){
doAction({ ID: input });
}
</script>
The action calls a mutation in store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
IDs: []
},
mutations: {
doAction: (state, id) => { state.IDs.push(id) }
},
actions: {
doAction: ({ commit }, id) => { commit('doAction', id) }
}
})
I also have a main.js that sets up the vue:
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
el: '#app',
store,
render: h => h(App)
})
The error I'm getting is:
ReferenceError: doAction is not defined
at runFunction
How can I call the mapped action inside a function? Version is Vue 2.6.10
There are several problems with defining runFunction as a 'local function':
function runFunction(input){
doAction({ ID: input });
}
Firstly, this is just a normal JavaScript function and the usual scoping rules apply. doAction would need to be defined somewhere that this function can see it. There is no magic link between this function and the component defined in App.vue. The function will be accessible to code in the component, such as in buttonClicked, but not the other way around.
The next problem is that it won't be available within your template. When you write runTemplate(1) in your template that's going to be looking for this.runTemplate(1), trying to resolve it on the current instance. Your function isn't on the current instance. Given your template includes #click="runFunction(1)" I'm a little surprised you aren't seeing a console error warning that the click handler is undefined.
mapActions accesses the store by using the reference held in this.$store. That reference is created when you add the store to your new Vue({store}). The store may appear to be available by magic but it's really just this.$store, where this is the current component.
It isn't really clear why you're trying to write this function outside of the component. The simplest solution is to add it to the methods. It'll then be available to the template and you can access doAction as this.doAction.
To keep it as a separate function you'd need to give it some sort of access to the store. Without knowing why you want it to be separate in the first place it's unclear how best to achieve that.
Of course it is not defined outside your instance .... you have to import the exported store from store.js on your function component :
<script>
import { mapActions } from 'vuex'
import store from 'store.js'
export default{
data() { return { } },
methods: {
...mapActions(['doAction']),
buttonClicked: (input) => { runFunction(input) }
}
}
function runFunction(input){
store.commit({ ID: input });
}
</script>

Sharing data between components in vue.js

I got an array of data in one component which I want to access in another component but cannot get it right
My idea was to just import component one in component two and thought I could access the data in that way but it didnt work.
here is what I got so far ...
Component 1:
export default {
data() {
return {
info: [
{
id: 1,
title: "Title One"
},
{
id: 2,
title: "Title Two"
},
Component 2:
<template>
<div>
<div v-for="item in info" v-bind:key="item.id">
<div>{{ item.title }} </div>
</div>
</div>
</template>
<script>
import ComponentOne from "../views/ComponentOne ";
export default {
components: {
ComponentOne
}, But after this I am a bit lost
Can anyone point my to the right direction it would be very much appreciated!
In order to access shared data, the most common way is to use Vuex. I'll get you going with the super basics with a module system as it does take a little reading.
npm install vuex --save
Create new folder called store in the src directory.
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import example from './modules/example'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
example // replace with whatever you want to call it
}
})
src/main.js
// add to your imports
import store from './store/index'
...
// Change your Vue instance init
new Vue({
router,
store, // <--- this bit is the thing to add
render: h => h(App)
}).$mount('#app')
/src/store/modules/example.js
// initial state
const state = {
info: []
}
// getters
const getters = {}
// actions
const actions = {
}
// mutations
const mutations = {
set (state, newState) {
state.info.splice(0)
state.info.push.apply(state.info, newState)
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
To update the store when you get info, from any component you can use this.$store.commit('example/set', infoArray) where the first parameter follows the pattern of module name/mutation function name, and the second parameter is the 'new state' that you want updated.
To access the data from the store, you can access it from your components as a computed property:
computed: {
info () {
return this.$store.state.example.info
}
}
Obviously you can use getters and actions and other stuff, but this will get you going and you can read up and modify the Vuex store once you get comfortable and understand how it works.
Let's say if you do not want to use any other state management like vuex then you can share with the use of mixins.
Well, you can achieve it with the use of Vue.mixins.
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixins will be “mixed” into the component’s own options.
Mixins official docs
Hope this helps!

VueX mutation works, but component computed property doesn't

I've read a lot of questions and answers with regard to this problem, but I don't seem to be doing any of the issues described before.
I have moved to using modules for my states, but after setting everything up, I can see that both local state of the store module and the getter gets updated. But the computed property doesn't. I cannot figure out why the hell it is happening, so here I am looking for help.
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import base_states from './modules/base_states'
import dialogs from './modules/dialogs'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
base_states,
dialogs,
},
})
dialogs.js
// initial state
const state = {
signInModalDialogComponent: 'signOn',
attendanceModalDialogComponent: 'AttendanceDetails',
dialogSignInVisible: false,
dialogDonationVisible: false,
}
// getters
const getters = {
signInModalDialogComponent: state => state.signInModalDialogComponent,
attendanceModalDialogComponent: state => state.attendanceModalDialogComponent,
dialogSignInVisible: state => state.dialogSignInVisible,
dialogDonationVisible: state => state.dialogDonationVisible,
}
// actions
const actions = {}
// mutations
const mutations = {
changeComponent(state, data){
state.signInModalDialogComponent = data
},
changeAttendanceComponent(state, componentName){
state.attendanceModalDialogComponent = componentName
},
toggleSignInVisibility(state, data){
state.dialogSignInVisible = data
},
changeDonationVisibility(state, data){
state.dialogDonationVisible = data
},
}
export default {
state,
getters,
actions,
mutations
}
component (I took out a bunch of code, which is not related)
<template lang="pug">
...
el-dialog(width='300px', :visible.sync='dialogSignInVisible')
component(
:is='signInModalDialogComponent',
#componentchanged='dialogComponent = $event'
)
</template>
<script type="text/javascript">
import { mapGetters, mapMutations } from 'vuex';
export default {
computed: {
...mapGetters([
'dialogSignInVisible',
'signInModalDialogComponent',
]),
},
methods: {
...mapMutations([
'toggleSignInVisibility'
]),
}
}
</script>
Specifically, I am looking at dialogSignInVisible which doesn't change. As you can see from the dev tools, mutation goes through and changes both the local state and getter. But when I look at the component, the computed vuex binding doesn't change.
The problem was that I was using a different Vue executable, in my store vs my webpack pack.
I use import Vue from 'vue/dist/vue.esm' everywhere, but in store I used import Vue from 'vue'. After making sure they are the same, everything worked without problems.

Vuex data store not found in components

I'm using a project with Vue, Vuetify, Vue-Router, and Vuex. The intent was to create a basic layout with a sidebar in a more module approach to dabble in scalability with Vue. So I created a folder called Store, which has a modules folder. So my index file within the store folder is as follows:
import Vue from 'vue';
import Vuex from 'vuex';
import global from './Modules/Global';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
site: global
}
});
The module is broken down into a single file with actions, getters, mutations, and state.
const actions = {
sidebarState: ({ commit }, status) => {
commit('openOrCloseSidebar', status);
}
}
const mutations = {
openOrCloseMenu: (status) => {
if (status !== true)
return state.sidebar = true;
return state.sidebar = false;
}
};
const getters = {
};
const state = {
sidebar: true
};
export default {
namespaced: true,
actions,
mutations,
getters,
state
};
I invoke the Vue instance as follows.
import Vue from 'vue/dist/vue';
import Vuetify from 'vuetify';
import Axios from 'axios';
import application from './Template/Application.vue';
import router from './Router';
import store from './Store';
import { sync } from 'vuex-router-sync';
Vue.use(Vuetify);
Vue.use(router);
Vue.use(store);
sync(store, router);
var vue = new Vue({
el: '#application',
template: '<application></application>',
components: {
application
},
router: router,
store: store
});
However, when I call this.$store.global.state.sidebar or this.$store.state.sidebar Vue is unable to find my property. I receive the error:
Cannot read property global of undefined.
The error also references state, but I believe since I'm using namespace the syntax should mirror above. Where I attempt to call that is here.
<template>
<v-container>
<application_sidebar :my-prop="menu"></application_sidebar>
<application_navigation :my-prop="menu"></application_navigation>
</v-container>
</template>
<script type="text/javascript">
import application_navigation from './Navigation.vue'
import application_sidebar from './Sidebar.vue';
import { mapState } from 'vuex';
export default ({
components: {
application_navigation,
application_sidebar
},
data: {
menu: this.$store.global.state.sidebar
}
});
</script>
I'm trying to access my state and learn how to correctly emit, so in the navigation component I can emit upward so the value is reflected to move the sidebar open or close.
Any help would be terrific, I'm quite new to Vue.
I think the main problem is your path to your module state is meant to be this.$store.state.site.
The recommended method is to use computed properties. For example
computed: {
menu() {
return this.$store.state.site.sidebar
}
}
You can also use the mapState helper
import { mapState } from 'vuex'
export default {
computed: mapState({ menu: state => state.site.sidebar })
}
The this variable does not reference the Vue instance when you are trying to access the store via this.$store.
The data object needs to be a method that returns an object.
data() {
return { menu: this.$store.state.site.sidebar };
}
However, by retrieving the value from the store's state object the data method like this, you are only setting the value of the menu data property when the Vue instance initializes. The value of menu will not update in response to changes to the value in the store's state.
If you need the menu value to be reflective of the state object throughout the life of the Vue instance, then you'd need to use a computed property or mapState, as suggested in #Phil's answer.