Vuex - Sharing common functions across modules - javascript

I am working on a Vue Js 2 application and I'm currently building the store and the different modules to separate out the code.
Is there a way to write a common function and share it across all modules?
For example, I have a function truncate() that I need to be use in customer.js, cart.js, address.js.
If I declare it in store.js and try to use in modules, it throws an error.
Is export and import the only way?
What is the best way to share the function?

The simplest case is, naturally, to just define a regular function in a js file and import/use it anywhere you need it.
There are Vue-specific approaches, though:
For common reusable functions in Vuex modules, you can use Vuex Plugins.
Check an example below. Mind the usage at the root store: plugins: [myTruncatePlugin].
const myTruncatePlugin = store => {
store.truncate = function(str) {
return str.replace(/-/g, '') + ' ...was truncaaaated!'; // example implementation
}
}
const moduleA = {
namespaced: true,
state: {name: "name#moduleA"},
mutations: { changeName(state, data) { state.name = this.truncate(data); } },
}
const moduleB = {
namespaced: true,
state: {title: "title#moduleB"},
mutations: { changeTitle(state, data) { state.title = this.truncate(data); } },
}
const myStore = new Vuex.Store({
strict: true,
modules: {
aaa: moduleA,
bbb: moduleB
},
plugins: [myTruncatePlugin] // IMPORTANT: YOU MUST DECLARE IT HERE
});
new Vue({
store: myStore,
el: '#app',
mounted: function() {
setTimeout(() => {
this.changeName("-n-e-w-N-A-M-E-");
this.changeTitle("-n-e-w-T-I-T-L-E-");
}, 200);
},
computed: {
...Vuex.mapState('aaa', ['name']),
...Vuex.mapState('bbb', ['title'])
},
methods: {
...Vuex.mapMutations('aaa', ['changeName']),
...Vuex.mapMutations('bbb', ['changeTitle'])
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<p>moduleA's name: {{ name }}</p>
<p>moduleB's title: {{ title }}</p>
</div>
For common reusable functions in Vue instances, you can use Mixins. For the most general case there's the Global Mixin (use with care):
Vue.mixin({
methods: {
truncate(str) {
return str.replace(/-/g, '') + ' ...was truncaaaated!'; // example implementation
}
}
})
// this.truncate() will be available in all Vue instances...
new Vue({
el: '#app1',
data: {myStr1: '-o-n-e-'},
mounted() { this.myStr1 = this.truncate(this.myStr1); }
})
new Vue({
el: '#app2',
data: {myStr2: '-t-w-o-'},
mounted() { this.myStr2 = this.truncate(this.myStr2); }
})
// ...and components
Vue.component('my-comp', {
template: '#t3',
data() { return {myStr3: '-t-h-r-e-e-'} },
mounted() { this.myStr3 = this.truncate(this.myStr3); }
});
new Vue({
el: '#app3',
})
<script src="https://unpkg.com/vue#2.5.16/dist/vue.min.js"></script>
<div id="app1">App1: "{{ myStr1 }}"</div>
<div id="app2">App2: "{{ myStr2 }}"</div>
<template id="t3">
<div>App3's component: "{{ myStr3 }}"</div>
</template>
<div id="app3"><my-comp></my-comp></div>

#acdcjunior has the best answer using Mixins, but I'm giving you another option by just declaring method in your Vue instance.
So belows example, I am creating doTruncate method in Vue instance then the components are calling them by this.$parent.doTruncate
// register
Vue.component('cart-component', {
template: '<button #click="doTruncate">Cart Truncate!</button>',
methods: {
doTruncate: function() {
this.$parent.doTruncate("Hello from cart");
}
}
})
// register
Vue.component('customer-component', {
template: '<button #click="doTruncate">Customer Truncate!</button>',
methods: {
doTruncate: function() {
this.$parent.doTruncate("Hello from customer");
}
}
})
var app3 = new Vue({
el: '#app',
methods: {
doTruncate: function(params) {
alert(params);
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.15/dist/vue.js"></script>
<div id="app">
<cart-component></cart-component>
<br>
<customer-component></customer-component>
<br>
<button #click="doTruncate('Hello from parent')">
Parent!
</button>
</div>

you can use vue js events to share function like
eventBus.js // it will create common instance
import Vue from 'vue';
export const eventBus = new Vue();
common.js // your common functions will go into this file
import { eventBus } from '<path of file>';
mounted() {
eventBus.$on('truncate',()=> {
this.truncate();
})
}
methods: {
truncate(){
//truncate code
}
}
customer.js // call your common truncate function from customer.js
import { eventBus } from '<path of file>';
eventBus.$emit('truncate');

Related

[Nuxt, i18n, Vuex]: How to access this.$i18n.locales from store getter

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;
}

Accessing instance data in Vue from Controller or Components

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.

VueJS Syntax: Saving a value in a Promise

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>

Vue.js : Passing an external variable to a component through initialization?

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
}

vuex: count is not defined in vue component file

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.)

Categories

Resources