Vuejs 3 Use the globalProperties in VUEX - javascript

Just a quick question,
I'm using VueJS 3 and VUEX state management.
app.config.globalProperties.store_id = '5f82da561622f55328d8baac'
this is the global property that I use and wondering how I can directly access it with VUEX.
https://v3.vuejs.org/api/application-config.html#globalproperties

You could define that store_id as state in your store which could be used in the store and also in any component you want :
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
store_id: '5f82da561622f55328d8baac',
//other state
}
},
mutations: {
}
})

Related

Vue3 | Pinia - Watching storeToRefs in composable function does not work

I'm trying to understand the purpose of composables. I have a simple composable like this and was trying to watch state from a Pinia store where the watch does not trigger:
import { ref, watch, computed } from "vue";
import { storeToRefs } from "pinia";
import useFlightsStore from "src/pinia/flights.js";
import usePassengersStore from "src/pinia/passengers.js";
export function useFlight() {
const route = useRoute();
const modalStore = useModalStore();
const flightsStore = useFlightsStore();
const { selection } = storeToRefs(flightsStore);
const passengersStore = usePassengersStore();
const { passengers, adults, children, infants } =
storeToRefs(passengersStore);
watch([adults, children, infants], (val) => console.log('value changes', val))
Where as the same thing in a Vue component works as expected.
So we cannot watch values inside composables?
I think you can watch values inside composables.
But, to watch a pinia state it has to be inside an arrow function:
watch(() => somePiniaState, (n) => console.log(n, " value changed"));
It's like watching a reactive object.
I believe this should be documented better. In Pinia documentation we can read how to watch the whole store or how to subscribe to a store but not how to watch a single state property inside a component or composable.
Also, the docs are somewhat shy in explaining that you can watch a property inside a store using setup() way of describing a store.
More on this here:
https://github.com/vuejs/pinia/discussions/794#discussioncomment-1643242
This error also silently fails (or does not execute), which is not helpful...
I needed to watch a specific state attribute in one of my components but I didn't find my use case on the official documentation.
I used a mix between a watch and storeToRefs to do it.
import { usePlaylistsStore } from '#/stores/playlists'
import { storeToRefs } from 'pinia'
export default {
name: 'PlaylistDetail',
setup() {
const playlistsStore = usePlaylistsStore()
const { selectedGenres } = storeToRefs(playlistsStore)
return { selectedGenres }
},
watch: {
selectedGenres(newValue, oldValue) {
// do something
}
}
}

Why does my vuex state change on changing my component level state even if I have not binded the vuex state to any html input element?

I have a vue store which has the following
store.js
import Vue from 'vue'
import Vuex from 'vuex'
const state = {
supplementStore: {}
}
const actions = {
getDataFromApi ({dispatch, commit}) {
APIrunning.then(response => {
commit('SET_SUPPLEMENT', response)
})
}
}
const mutations = {
SET_SUPPLEMENT (state, data) {
state.supplementStore= data
}
}
const foodstore = {
namespaced: true,
state,
actions,
mutations
}
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
foodstore
}
})
My vue component looks like this
Supp.vue
<template>
<input type="checkbox" v-model="supps.logged">
</template>
<script>
import {mapState, mapActions} from 'vuex'
import store from './store'
export default {
data () {
return {
supps: []
}
},
mounted () {
this.supps = this.supplementStore
},
computed: {
...mapState('foodstore', ['supplementStore'])
}
}
</script>
As you can see I have a component level state called supps which is assigned the value of supplementStore (which is a vuex state) as soon as it is mounted.
mounted () {
this.supps = this.supplementStore
},
supplementStore gets its value from the the API and it is a JSON object which looks like this
supplementStore = {
logged: true
}
Therefore, when my Supp.vue component is mounted my local state supps will become
supps = {
logged: true
}
supps is binded to an input field of type checkbox (Supp.vue) using the v-model directive.
What I want to achieve:
When I toggle the checkbox, supps.logged should toggle between true and false but, supplementStore.logged should remain unchanged (since I have not binded it to my input field).
What I observe in my Vue Devtools:
When I toggle the checkbox, both supps.logged AND supplementStore.logged are toggling in sync i.e both of them are toggling in sync between true and false, whereas I want only supps.logged to get toggled.
Can anyone help me?
In Javascript, object is passed by reference. (This is a reasonably good explanation => https://medium.com/nodesimplified/javascript-pass-by-value-and-pass-by-reference-in-javascript-fcf10305aa9c)
To avoid this problem, you can clone the object when assigning to supps.
mounted () {
this.supps = { ...this.supplementStore } // cloning the object using Javascript Spread syntax
},
Have you tried Object.assign instead? In JS objects are passed by reference. Assigning one to a variable will cause the original one to change if the variable is changed inside.
To clone an object, you may try this:
// this.assignedObj = new object.
// this.obj = original object.
this.assignedObj = Object.assign({}, this.obj);
JSFiddle: https://jsfiddle.net/mr7x4yn0/
Edit: As you can see from the demo, Vue.set or this.$set will not work for you (probably).
The data I was receiving from the API into supplementStore was in the form of an array of objects:
supplementStore = [
{
"logged": true
},
{
"logged": false
}
]
And as Jacob Goh and Yousof K. mentioned in their respective answers that objects and arrays get passed by reference in javascript, I decided to use the following code to assign the value of supplementStore to supps inside my mounted() hook:
mounted () {
let arr = []
for (let i = 0; i < this.supplementStore.length; i++) {
let obj = Object.assign({}, this.supplementStore[i])
arr.push(obj)
}
this.supps = arr
}
Now when I toggle my checkbox, supplementStore.logged remains unchanged while supps.logged toggles between true and false just the way I wanted.

VueJS: Best practice for working with global object between components?

there is User.js class and user object(user = new User();).
The user object is being used in all nested components. in User class there are so many important methods.
How can I simply use/access this.user or this.$user and its methods in any component?
1-solution (temporary working solution): Setting user in vuex's store and define in all components' data:
data(){
return {
user:this.$store.state.user
}
}
Cons: in every component, this should be added. Note: there are so many components.
2-solution: adding user to Vue's prototype like plugin:
Vue.prototype.$user = user
Cons: when user's data changes, it doesn't effect in DOM element (UI).
3-solution: putting to components's props.
Cons: in every component, this should be added. Note: Again there are so many components.
All of the solutions I found have issues, especially as the project gets larger and larger.
Any suggestion and solution will be appreciated!
Note: Applies for Vue 2x
Proposal 1: Using getters from vuex
You could use getters along with mapGetters from Vuex to include users within computed properties for each component.
Vuex
getters: {
// ...
getUser: (state, getters) => {
return getters.user
}
}
component
import { mapGetters } from 'vuex'
computed: {
...mapGetters([getUser])
}
Proposal 2: add a watcher via plugin
Vue
// When using CommonJS via Browserify or Webpack
const Vue = require('vue')
const UserPlug = require('./user-watcher-plugin')
// Don't forget to call this
Vue.use(UserPlug)
user-watcher-plugin.js
const UserPlug = {
install(Vue, options) {
// We call Vue.mixin() here to inject functionality into all components.
Vue.watch: 'user'
}
};
export default UserPlug;
Proposal 3: add a computed property user as plugin via mixin
Vue
// When using CommonJS via Browserify or Webpack
const Vue = require('vue')
const UserPlug = require('./user-watcher-plugin')
// Don't forget to call this
Vue.use(UserPlug)
user-watcher-plugin.js
const UserPlug = {
install(Vue, options) {
// We call Vue.mixin() here to inject functionality into all components.
Vue.mixin({
computed: {
user: function() {
return this.$store.state.user
}
}
})
}
};
export default UserPlug;
Based on #Denis answer, specifically Proposal 3, Here is the UserPlugin.js:
import store from '#/store/store';
import User from './User';
const UserPlugin = {
install(Vue) {
const $user = new User();
window.$user = $user;
store.commit('setUser', $user);
Vue.mixin({
computed: {
$user() {
return store.state.user;
}
}
});
}
};
export default UserPlugin;
and main.js:
import UserPlugin from './common/UserPlugin';
Vue.use(UserPlugin);
new Vue({
render: h => h(App)
}).$mount('#app');
For further usage, I published small library for solving these kinda issues:
https://www.npmjs.com/package/vue-global-var
Assuming you don't actually use all methods/attributes of user in every component, but a subset of them everytime, I don't see any reason why solution 1 & 2 do not work for you, since passing the whole user object to every component is not necessary.
Let's say your object User have some attributes (a1, a2, a3, etc.) and methods (m1, m2, m3...). If a component only needs some of them (e.g. a1, a2, m1, m2, m3) then with Vuex, you can use mapping functions (mapState, mapGetters, mapMutations and mapActions) to get the exact info from user
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
...mapState('user', [ 'a1' ]),
...mapGetters('user', [ 'a2' ])
},
methods: {
...mapMutations('user', [ 'm1' ]),
...mapActions('user', [ 'm2', 'm3' ])
}
}
For solution 2 (using prototype), to make component update when user data changes, you can map the necessary data to component via methods.
export default {
methods: {
userA1() {
return this.$user.attributes.a1;
},
userM1() {
this.$user.methods.m1();
}
// and so on
}
}
Even better, you can create mixins to explicitly map data from user, and reuse your mixins to avoid duplicated code in components. It can be applied for both Vuex solution and prototype solution.
// mixin1:
const mixin1 = {
computed: {
...mapState('user', [ 'a1' ]),
},
methods: {
...mapMutations('user', [ 'm1' ])
}
}
// mixin2:
const mixin2 = {
computed: {
...mapGetters('user', [ 'a2' ]),
},
methods: {
...mapActions('user', [ 'm2', 'm3' ])
}
}
// component1
export default {
mixins: [ mixin1 ]
}
// component 2
export default {
mixins: [ mixin1, mixin2 ]
}
But if you really need to pass the whole object user to every component, then nothing could do. Rather, you should review your implementation and see if there is any better way to break the object into smaller meaningful ones.
You can use mixins to add User.js to your root component like
import userLib from './User';
//User.js path should correct
Then
var app = new Vue({
router,
mixins: [
userLib
],
//.....
});
After that you can use any of these User method in your any component like
this.$parent.userClassMehtod();
or if any data access
this.$parent.userClassData;
Finally dont forget to add export default{//..} in User.js
Note: This is only work if you export all method of User.js into export default
I just created the minimal codesandbox to clear the idea of how dependency Injection works in vue.
You can have a second Vue instance and declare a reactive property.
See: Reactivity in depth

accessing vuex store in js file

Just like in main.js, I'm trying to access my store from a helper function file:
import store from '../store'
let auth = store.getters.config.urls.auth
But it logs an error:
Uncaught TypeError: Cannot read property 'getters' of undefined.
I have tried
this.$store.getters.config.urls.auth
Same result.
store:
//Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
config: 'config',
},
getters: {
config: state => state.config
},
});
export default store
How do I make my store available outside of components?
The following worked for me:
import store from '../store'
store.getters.config
// => 'config'
This Worked For Me In 2021
I tried a bunch of different things and it seems, at least in Vue 3, that this works. Here is an example store:
export default {
user: {
bearerToken: 'initial',
},
};
Here is my Getters file:
export default {
token: (state) => () => state.user.bearerToken,
};
Inside your .js file add the page to your store\index.js file.
import store from '../store';
In order to access the getters just remember it is a function (which may seem different when you use mapGetters.)
console.log('Checking the getters:', store.getters.token());
The state is more direct:
console.log('Checking the state:', store.state.user.bearerToken);
If you are using namespaced modules, you might encounter the same difficulties I had while trying to retrieve items from the store;
what might work out for you is to specify the namespace while calling the getters (example bellow)
import store from '../your-path-to-your-store-file/store.js'
console.log(store.getters.['module/module_getter']);
// for instance
console.log(store.getters.['auth/data']);
put brackets on your import and it should work
import { store } from '../store'
using this approach has worked for me:
// app.js
import store from "./store/index"
const app = new Vue({
el: '#app',
store, //vuex
});
window.App = app;
// inside your helper method
window.App.$store.commit("commitName" , value);
if you are using nuxt you can use this approach
window.$nuxt.$store.getters.myVar
if you have multiple modules
window.$nuxt.$store.getters['myModule/myVar']
export default ( { store } ) => {
store.getters...
}

Separate vuex stores for component created with v-for

Imagine three components First.vue, Second.vue and Third.vue
They share some info, to manage all changes and get access to data I use Vuex.
It works perfect. No problem here. To keep store clean and tidy, I have modules for every component.
Then, we have a component named Card.vue. It is composed of three components, mentioned above.
In App.vue, I use v-for directive to create as many Cards as needed.
<template>
<div>
<card v-for="card in cards" :id="card"></card
</div>
</template>
<script>
export default {
data () {
return {
cards: [] // items being dynamically added to this array
}
</script>
As you understand, when we generate more than one of Card.vue, we have several cards with the same data. In other words when I change any value in input field of Card#1, I commit change to $store, and update state everywhere else (in Card#2, Card#3 and so on...) What I need is to separate those Cards data. How can I design my Vuex to accomplish that goal.
Sharing STATE and COMPONENTS
$store
import job from './modules/job'
import paper from './modules/paper'
import admin from './modules/admin'
import * as getters from './getters'
import * as mutations from './mutations'
export const store = new Vuex.Store({
getters,
mutations,
modules: {
job,
paper,
admin
}
})
I use three modules and keep my getters and mutations in separate js file. Module files have the same structure. For example, Job.js is like:
const state = {
paper: {
paperWidth: null,
paperHeight: null,
typeOfPaper: null,
...// other state data
}
}
const getters = {
paper: state => state.paper
}
const mutations = {
updatePaper (state, paper) {
Object.assign(state.paper, paper)
},
...// other local mutations go here
export default {
state,
getters,
mutations
}
I've no actions. No need as of now.
Three components are Job.vue, Paper.vue and Rseults.vue. They get state and mutations from their respective module and also from global store.
import {mapMutations, mapGetters} from 'vuex'
export default {
computed: {
...mapGetters([
'job',
'paper',
...// other shared mutations from store
])
},
methods: {
...mapMutations([
'fourPlusFour',
'fourPlusZero',
'updateJob',
...// other shared mutations from store
])
}
}

Categories

Resources