How does Vuex 4 createStore() work internally - vue.js

I am having some difficulty understanding how Veux 4 createStore() works.
In /store/index.js I have (amongst a few other things):
export function createVuexStore(){
return createStore({
modules: {
userStore,
productStore
}
})
}
export function provideStore(store) {
provide('vuex-store', store)
}
In client-entry.js I pass the store to makeApp() like this:
import * as vuexStore from './store/index.js';
import makeApp from './main.js'
const _vuexStore = vuexStore.createVuexStore();
const {app, router} = makeApp({
vuexStore: _vuexStore,
});
And main.js default method does this:
export default function(args) {
const rootComponent = {
render: () => h(App),
components: { App },
setup() {
vuexStore.provideStore(args.vuexStore)
}
}
const app = (isSSR ? createSSRApp : createApp)(rootComponent);
app.use(args.vuexStore);
So, there is no store that is exported from anywhere which means that I cannot import store in another .js file like my vue-router and access the getters or dispatch actions.
import {store} '../store/index.js' // not possible
In order to make this work, I did the following in the vue-router.js file which works but I don't understand why it works:
import * as vuexStore from '../store/index.js'
const $store = vuexStore.createVuexStore();
async function testMe(to, from, next) {
$store.getters('getUser'); // returns info correctly
$store.dispatch('logout'); // this works fine
}
Does Veux's createStore() method create a fresh new store each time or is it a reference to the same store that was created in client-entry.js? It appears it is the latter, so does that mean an application only has one store no matter how many times you run createStore()? Why, then, does running createStore() not overwrite the existing store and initialise it with blank values?

createStore() method can be used on your setup method.
On your main.js, you could do something like this
import { createApp } from 'vue'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
store.js
import { createStore } from 'vuex';
export default createStore({
state: {},
mutations: {},
actions: {},
});
To access your store, you don't need to import store.js anymore, you could just use the new useStore() method to create the object. You can directly access your store using it just as usual.
your-component.js
<script>
import { computed } from "vue";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const isAuthenticated = computed(() => store.state.isAuthenticated);
const logout = () => store.dispatch("logout");
return { isAuthenticated, logout };
},
};
</script>
To use your store in the route.js file, you could simply imported the old fashion way.
route.js
import Home from '../views/Home.vue'
import store from '../store/'
const logout = () => {
store.dispatch("auth/logout");
}
export const routes = [
{
path: '/',
name: 'Home',
component: Home
}
]

Related

Custom-Component | Cannot access 'store' before initialization

I want to access the vuex store inside my custom-component. I create the component like:
import {
defineCustomElement
} from 'vue';
import expensemain from '~/components/expense_editor/Main.ce.vue';
const CustomElement = defineCustomElement(expensemain);
window.customElements.define('expense-custom', CustomElement);
And import the store like:
import store from "../../store/store.js";
export default {
props: {
data: JSON,
expense_voucher_data: JSON
},
setup(props) {
let store = store.state.expense;
console.log(store);
But can't access it cause it seems not to be initialized.
Inside the store.js it is, tho:
const store = createStore({
modules: {
signup,
expense
}
});
export default store;
I can't use app.use inside main.js cause it's a custom component. How would I import the store to be able to use it?
I nearly got it right. The solution was as simple as it could've been:
import store from "../../store/store.js"; // import created store
export default {
props: {
data: JSON,
expense_voucher_data: JSON
},
setup(props) {
store.state.moduleName // direct access to module state
store.getters // getters
store.dispatch // actions
store.commit // mutations
Please try:
import store from "../../store/store.js";
import { useStore } from 'vuex'
export default {
props: {
data: JSON,
expense_voucher_data: JSON
},
setup(props) {
// let store = store.state.expense;
const store = useStore();
console.log(store);
And if it dosen't work, you'd better check Vuex guide of Composition API.

Access app.config.globalProperties in vuex store

I got a vuex store like this:
const state = {
status: '',
};
const getters = {
//...
};
const actions = {
// ...
};
const mutations = {
// ...
};
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}
Now I'd like to access app.config.globalProperties.$notify
In Vue.js2 I was using something like Vue.prototype.$notify, but this is not working anymore.
$notify is also provided like this:
app.use(routes)
.provide('notify', app.config.globalProperties.$notify)
.mount('#app')
Unfortunately I did not find any information about this in the docs yet.
So my question: How can I either inject $notify or access app.config.globalProperties within this store?
From your store and its modules, you could return a store factory -- a function that receives the application instance from createApp and returns a store:
// store/modules/my-module.js
const createStore = app => {
const mutations = {
test (state, { committedItem }) {
app.config.globalProperties.$notify('commited item: ' + committedItem)
}
}
return {
namespaced: true,
//...
mutations,
}
}
export default app => createStore(app)
// store/index.js
import { createStore } from 'vuex'
import myModule from './modules/my-module'
export default app =>
createStore({
modules: {
myModule: myModule(app)
}
})
Then use the store factory like this:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import createStore from './store'
const app = createApp(App)
app.use(createStore(app)).mount('#app')
demo

Problem importing getters into Router - Vuex

Problem importing getters into Rotate - Vuex.
I am trying to import a value that is within the vuex state.
An error is reported, stating that it is undefined.
I have no idea what I might have done wrong. Please, if anyone can help, I will be very grateful.
Thanks for listening
Error
TypeError: "_store__WEBPACK_IMPORTED_MODULE_4__.default.getters is undefined"
Store
import Vue from 'vue'
import Vuex from 'vuex'
import auth from './module-auth'
Vue.use(Vuex)
export default function () {
const Store = new Vuex.Store({
modules: {
auth
},
strict: process.env.DEV
})
return Store
}
module-auth
Getters
import decode from 'jwt-decode'
function isTokenExpired (state) {
try {
const decoded = decode(state.token)
if (decoded.exp < Date.now() / 1000) {
return true
} else return false
} catch (err) {
return false
}
}
export {
isTokenExpired,
}
Router
import Vue from 'vue'
import VueRouter from 'vue-router'
import routes from './routes'
import store from '../store'
Vue.use(VueRouter)
export default function () {
const Router = new VueRouter({
scrollBehavior: () => ({ x: 0, y: 0 }),
routes,
mode: process.env.VUE_ROUTER_MODE,
base: process.env.VUE_ROUTER_BASE
})
Router.beforeEach((to, from, next) => {
const publicPages = ['/']
const authRequired = !publicPages.includes(to.path)
const loggedIn = store.getters['auth/isTokenExpired']
console.log(loggedIn)
if (authRequired && !loggedIn) {
return next('/')
}
next()
})
return Router
}
Your mistake is that you try to use a function as Vuex module.
Module should be an object.
Docs say:
export const moduleA = {
state: { count: 0 },
mutations: {
increment(state) {
state.count++;
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
}
};
And your function isTokenExpired looks like it should be placed in "getters" section.
Exporting a function that create a store and use it as a function will create many stores and is not desired.
Since you need to use one instance of store anywhere, you need to export the store instance, not a function that create a store.

Issues with registering Vuex modules, either namespaces not found or getters.default is empty

I've been going in circles trying to set up Vuex modules for a project. I've tried following a tweaked example of Chris Fritz example of registering components in a more productive way as seen here: https://www.youtube.com/watch?v=7lpemgMhi0k, but when I try to use ...mapActions, I get issues that the namespace can't be found, if I try to register the modules manually, I get the error that the getter.default expected an action, but returned {}
I'll try keep this clean - the first lot of info is my module setup, the next section is what produces the first issue and the code, the second is the second attempt which produces the getter issue.
My Module setup...
./store/modules/ModuleName:
./store/modules/ModuleName/index.js
import * as actions from './actions'
import * as getters from './getters'
import * as mutations from './mutations'
const namespaced = true
export default {
namespaced,
state () {
return {
foo: 'bar'
}
},
actions,
getters,
mutations
}
./store/modules/ModuleName/actions.js
import types from './types'
const myFunction = ({ commit, state }) => {
commit(types.FOO, bar)
}
export default {
myFunction
}
./store/modules/ModuleName/getters.js
const Foo = (state) => {
return state.foo
}
export default {
Foo
}
./store/modules/ModuleName/mutations.js
import types from './types'
const mutateFoo = (state, payload) => {
state.Foo = payload
}
export default {
[types.FOO]: mutateFoo
}
./store/modules/ModuleName/types.js
export default {
FOO: 'foo'
}
Version one
This is the preferred way I'd like to do it:
Error: [vuex] module namespace not found in mapActions(): myModule/
./store/modules.js
const requireModule = require.context('../store/modules/', true, /\.js$/)
const modules = {}
requireModule.keys().forEach(fileName => {
// Use index to prevent duplicates of the same module folder...
if (fileName.includes('index')) {
// now I just want the folder name for the module registration
const moduleName = fileName.replace(/(\.\/|\/.+\.js)/g, '')
modules[moduleName] = requireModule(fileName)
}
})
export default modules
./store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules
})
.Vue component:
<template>
...
</template>
<script>
import { mapState, mapActions } from 'vuex'
export default {
computed: {
...mapState('ModuleName', { title: state => state.foo })
},
methods: {
...mapActions('ModuleName, ['myFunction'])
}
}
</script>
Version Two
Error:
Uncaught Error: [vuex] getters should be function but
"getters.default" in module "Harvest" is {}.
./store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import Harvest from './modules/myModule'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
myModule
}
})
.Vue component:
<template>
...
</template>
<script>
export default {
computed: {
title () { return this.$store.getters.myModule }
}
}
</script>
What am I missing? Can someone help shed a bit of light to get this working?
For your Version two, because you export default, your import state should be:
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
const namespaced = true
export default {
namespaced,
state () {
return {
foo: 'bar'
}
},
actions,
getters,
mutations
}
For version 1, first you should change import statement as above, then change
if (fileName.includes('index')) {
// now I just want the folder name for the module registration
const moduleName = fileName.replace(/(\.\/|\/.+\.js)/g, '')
modules[moduleName] = requireModule(fileName).default
}
Hope it can help!

Access the state of store or getters? _WEBPACK_IMPORTED_MODULE_3__store___.a.dispatch is not a function

I am trying to verify if the user is authenticated to be able to give access to the route that is directed otherwise redirect to the login route, the problem is that I do not know how to execute the fetchUser action from my beforeEach. In other words, I can't access my getter from the router script.
store.js
import mutations from './mutations';
import actions from './actions';
import getters from './getters';
export default {
state: {
isLoggedIn: !!localStorage.getItem("token"),
user_data : localStorage.getItem("user_data"),
},
getters ,
mutations,
actions
}
routes/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
import routes from './rutas';
import store from '../store/';
const router = new VueRouter({
mode : 'history',
routes
})
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!store.getters.isLoggedIn) {
next({path: '/login'})
}
else {
store.dispatch('fetchUser') // Line error
next()
}
}
else {
next() // make sure to always call next()!
}
})
getters.js
export default {
isLoggedIn: state => {
return state.isLoggedIn
},
user_name : state =>{
if(! _.isEmpty(this.user_data))
return JSON.parse(state.user_data).name
return '';
},
isEmptyUser : state =>{
return _.isEmpty(this.user_data);
},
isAdmin: state => {
if(! _.isEmpty(this.user_data)) return state.user_data.nivel===1
return false;
}
}
actions.js
export default {
/* more methods*/
, async fetchUser({ commit }) {
return await axios.post('/api/auth/me')
.then(res => {
setTimeout(() => {
localStorage.setItem("user_data", JSON.stringify(res.data));
Promise.resolve(res.data);
}, 1000);
},
error => {
Promise.reject(error);
});
}
This returns error in console:
_WEBPACK_IMPORTED_MODULE_3__store___.a.dispatch is not a function
How can I do or the approach is the wrong one and I should not access actions directly?
The problem is your store is not the actual store object, it is just the object used to generate it.
A solution is to have the file export the real store:
import Vue from 'vue';
import Vuex from 'vuex';
import mutations from './mutations';
import actions from './actions';
import getters from './getters';
Vue.use(Vuex); // added here
export default new Vuex.Store({ // changed here
state: {
isLoggedIn: !!localStorage.getItem("token"),
user_data : localStorage.getItem("user_data"),
},
getters ,
mutations,
actions
}) // changed here
Now your router code would work.
What you must be aware as well is that somewhere, probably in your main.js, you had the store being initialized like above. For example:
import store from '../store/';
new Vue({
store: new Vuex.Store(store),
// ...
})
Now you must remove that initialization and use the store directly:
import store from '../store/';
new Vue({
store: store, // or simply store
// ...
})
And all should be good.