accessing vuex store in js file - javascript

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

Related

How to properly install a Pinia Store?

I'm building a Vue 3 app using the OptionsAPI along with a Pinia Store but I frequently run into an issue stating that I'm trying to access the store before createPinia() is called.
I've been following the documentation to use the Pinia store outside components as well, but maybe I'm not doing something the proper way.
Situation is as follows:
I have a login screen (/login) where I have a Cognito session manager, I click a link, go through Cognito's signup process, and then get redirected to a home route (/), in this route I also have a subroute that shows a Dashboard component where I make an API call.
On the Home component I call the store using useMainStore() and then update the state with information that came on the URL once I got redirected from Cognito, and then I want to use some of the state information in the API calls inside Dashboard.
This is my Home component, which works fine by itself, due to having const store = useMainStore(); inside the mounted() hook which I imagine is always called after the Pinia instance is created.
<template>
<div class="home">
<router-view></router-view>
</div>
</template>
<script>
import {useMainStore} from '../store/index'
export default {
name: 'Home',
components: {
},
mounted() {
const store = useMainStore();
const paramValues = {}
const payload = {
// I construct an object with the properties I need from paramValues
}
store.updateTokens(payload); // I save the values in the store
},
}
</script>
Now this is my Dashboard component:
<script>
import axios from 'axios'
import {useMainStore} from '../store/index'
const store = useMainStore();
export default {
name: "Dashboard",
data() {
return {
user_data: null,
}
},
mounted() {
axios({
url: 'myAPIUrl',
headers: { 'Authorization': `${store.token_type} ${store.access_token}`}
}).then(response => {
this.user_data = response.data;
}).catch(error => {
console.log(error);
})
},
}
</script>
The above component will fail, and throw an error stating that I'm trying to access the store before the instance is created, I can solve this just by moving the store declaration inside the mounted() hook as before, but what if I want to use the store in other ways inside the component and not just in the mounted hook? And also, why is this failing? By this point, since the Home component already had access to the store, shouldn't the Dashboard component, which is inside a child route inside Home have the store instance already created?
This is my main.js file where I call the createPinia() method.
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
const pinia = createPinia();
createApp(App).use(router).use(pinia).mount('#app')
And the error I get is:
Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?
My Store file:
import { defineStore } from 'pinia';
export const useMainStore = defineStore('main', {
state: () => ({
access_token: sessionStorage.getItem('access_token') || '',
id_token: sessionStorage.getItem('id_token') || '',
token_type: sessionStorage.getItem('token_type') || '',
isAuthenticated: sessionStorage.getItem('isAuthenticated') || false,
userData: JSON.parse(sessionStorage.getItem('userData')) || undefined
}),
actions: {
updateTokens(payload) {
this.id_token = payload.id_token;
this.access_token = payload.access_token;
this.token_type = payload.token_type
sessionStorage.setItem('id_token', payload.id_token);
sessionStorage.setItem('access_token', payload.access_token);
sessionStorage.setItem('token_type', payload.token_type);
sessionStorage.setItem('isAuthenticated', payload.isAuthenticated);
},
setUserData(payload) {
this.userData = payload;
sessionStorage.setItem('userData', JSON.stringify(payload));
},
resetState() {
this.$reset();
}
},
})
It's possible but not common and not always allowed to use use composition functions outside a component. A function can rely on component instance or a specific order of execution, and current problem can happen when it's not respected.
It's necessary to create Pinia instance before it can be used. const store = useMainStore() is evaluated when Dashboard.vue is imported, which always happen before createPinia().
In case of options API it can be assigned as a part of component instance (Vue 3 only):
data() {
return { store: useMainStore() }
},
Or exposed as global property (Vue 3 only):
const pinia = createPinia();
const app = createApp(App).use(router).use(pinia);
app.config.globalProperties.mainStore = useMainStore();
app.mount('#app');
Since you're using Vue 3, I suggest you to use the new script setup syntax:
<script setup>
import { reactive, onMounted } from 'vue'
import axios from 'axios'
import { useMainStore } from '../store'
const store = useMainStore();
const data = reactive({
user_data: null
})
onMounted (async () => {
try {
const {data: MyResponse} = await axios({
method: "YOUR METHOD",
url: 'myAPIUrl',
headers: { 'Authorization': `${store.token_type} ${store.access_token}`}
})
data.user_data = MyResponse
} catch(error){
console.log(error)
}
})
</script>
Using setup you can define that store variable and use it through your code.
everyone after a lot of research I found the answer to this issue,
you must pass index.ts/js for const like below:
<script lang="ts" setup>
import store from '../stores/index';
import { useCounterStore } from '../stores/counter';
const counterStore = useCounterStore(store());
counterStore.increment();
console.log(counterStore.count);
</script>

Vuex - store state Object is of type unknown

/src/middlewares/auth.ts file:
import store from '#/store'
export default {
guest(): void {
if (store.state.auth.authenticated === false) {
// do some action
}
}
}
/src/store.ts file:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
auth
}
})
/src/store/auth.ts:
import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'
#Module
export default class Auth extends VuexModule {
public authenticated: boolean = false
}
However i'm getting TS error: Object is of type 'unknown'
Then my npm run build fails. How can i type store so this error would disappear?
UPDATE 1
Problems is in line: if (store.state.auth.authenticated === false)
UPDATE 2
My directory structure for these 3 files:
/src/middlewares/auth.ts
/src/store.ts
/src/store/auth.ts
UPDATE 3
if i change store.state.auth.authenticated to store.state then compiler stops complaining, i think its related to my store auth.ts file, i need to type definition it somehow.
Take a look: logrocket
export default function auth ({ next, store }){
if(!store.getters.auth.loggedIn){
return next({
name: 'login'
})
}
return next()
}
Your first argument sohuld be an object defining { store }
export default guest ({ store }, to: any ...){
It's hard to tell where store comes from in your scenario that you posted. If you are importing it directly then it would be something along the lines of:
import store from "store/index";
Also, by the nature of the store it's not fully typed. You could type it with something like
if ((store as object).state?.auth?.isLogged) {}
Though this won't give you type safety it would tell the compiler that you don't know.

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

With Vue-cli, where do I declare my global variables?

In most Vue.js tutorials, I see stuff like
new Vue({
store, // inject store to all children
el: '#app',
render: h => h(App)
})
But I'm using vue-cli (I'm actually using quasar) and it declares the Vue instance for me, so I don't know where I'm supposed to say that I want store to be a "Vue-wide" global variable. Where do I specify that? Thanks
Yea, you can set those variables like this, in your entrypoint file (main.js):
Vue.store= Vue.prototype.store = 'THIS IS STORE VARIABLE';
and later access it in your vue instance like this:
<script>
export default {
name: 'HelloWorld',
methods: {
yourMethod() {
this.store // can be accessible here.
}
}
}
</script>
You can also see this in the vue-docs here.
Edit 1:
from the discussions in the comment sections about "no entrypoint file" in quasar's template.
what you can do is, to go to src/router/index.js, and there you will be able to get access to Vue, through which you can set a global variable like this:
...
import routes from './routes'
Vue.prototype.a = '123';
Vue.use(VueRouter)
...
and then if you console.log it in App.vue, something like this:
<script>
export default {
name: 'App',
mounted() {
console.log(this.a);
}
}
</script>
now, look at your console:
You can also do the same in App.vue file in the script tag.
You don't need to make the store a global variable like that, as every component (this.$store) and the Vue instance itself have access to the store after the initial declaration.
Take a look at the Quasar docs for App Vuex Store.
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
updateCount(state) {
state.count += 1
}
}
})
main.js
import App from './App.vue'
import store from '/path/to/store.js'
new Vue({
el: '#app',
store,
render: h => h(App)
})
If you need to access the store from within a component you can either import it (as we did in main.js) and use it directly [note that this is a bad practice] or access using this.$store. You can read a bit more about that here.
In any case here's the official Getting Started guide from Vuex team
We could add the Instance Properties
Like this, we can define instance properties.
Vue.prototype.$appName = 'My App'
Now $appName is available on all Vue instances, even before creation.
If we run:
new Vue({
beforeCreate: function() {
console.log(this.$appName)
}
})
Then "My App" will be logged to the console!
Slightly redundant to the aforementioned answer, but I found this to be simpler per the current Vuex state documentation at the time of this reply.
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default function (/* { ssrContext } */) {
const Store = new Vuex.Store({
modules: {
// example
},
state: {
cdn_url: 'https://assets.yourdomain.com/'
},
// for dev mode only
strict: process.env.DEV
})
return Store
}
...and then in your component, e.g. YourPage.vuex
export default {
name: 'YourPage',
loadImages: function () {
img.src = this.$store.state.cdn_url + `yourimage.jpg`
}
}
Joining the show a bit late, but the route I personally use in Quasar is to create a Boot file for my global constants and variables.
I create the Boot file (I call it global-constants.js but feel free to call it whatever).
/src/boot/global-constants.js
import Vue from 'vue'
Vue.prototype.globalConstants = {
baseUrl: {
website: 'https://my.fancy.website.example.com',
api: 'https://my.fancy.website.example.com/API/v1'
}
}
if (process.env.DEV) {
Vue.prototype.globalConstants.baseUrl.website = 'http://localhost'
Vue.prototype.globalConstants.baseUrl.api = 'http://localhost/API/v1'
}
if (process.env.DEV) {
console.log('Global Constants:')
console.log(Vue.prototype.globalConstants)
}
Then add a line in quasar.conf.js file to get your Boot file to kick:
/quasar.conf.js
module.exports = function (ctx) {
return {
boot: [
'i18n',
'axios',
'notify-defaults',
'global-constants' // Global Constants and Variables
],
Then to use it:
from Vuex
this._vm.globalConstants.baseUrl.api
for example: axios.post(this._vm.globalConstants.baseUrl.api + '/UpdateUserPreferences/', payload)
from Vue HTML page
{{ globalConstants.baseUrl.api }}
from Vue code (JavaScript part of Vue page
this.globalConstants.baseUrl.api
An alternative Vue3 way to this answer:
// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$appName = 'My App'
app.component('child-component', {
mounted() {
console.log(this.$appName) // 'My App'
}
})

How to get vuex state from a javascript file (instead of a vue component)

I am working with vuex (2.1.1) and get things working within vue single file components. However to avoid too much cruft in my vue single file component I moved some functions to a utils.js module which I import into the vue-file. In this utils.js I would like to read the vuex state. How can I do that? As it seems approaching the state with getters etc is presuming you are working from within a vue component, or not?
I tried to import state from '../store/modules/myvuexmodule' and then refer to state.mystateproperty but it always gives 'undefined', whereas in the vue-devtools I can see the state property does have proper values.
My estimate at this point is that this is simply not 'the way to go' as the state.property value within the js file will not be reactive and thus will not update or something, but maybe someone can confirm/ prove me wrong.
It is possible to access the store as an object in an external js file, I have also added a test to demonstrate the changes in the state.
here is the external js file:
import { store } from '../store/store'
export function getAuth () {
return store.state.authorization.AUTH_STATE
}
The state module:
import * as NameSpace from '../NameSpace'
/*
Import everything in NameSpace.js as an object.
call that object NameSpace.
NameSpace exports const strings.
*/
import { ParseService } from '../../Services/parse'
const state = {
[NameSpace.AUTH_STATE]: {
auth: {},
error: null
}
}
const getters = {
[NameSpace.AUTH_GETTER]: state => {
return state[NameSpace.AUTH_STATE]
}
}
const mutations = {
[NameSpace.AUTH_MUTATION]: (state, payload) => {
state[NameSpace.AUTH_STATE] = payload
}
}
const actions = {
[NameSpace.ASYNC_AUTH_ACTION]: ({ commit }, payload) => {
ParseService.login(payload.username, payload.password)
.then((user) => {
commit(NameSpace.AUTH_MUTATION, {auth: user, error: null})
})
.catch((error) => {
commit(NameSpace.AUTH_MUTATION, {auth: [], error: error})
})
}
export default {
state,
getters,
mutations,
actions
}
The store:
import Vue from 'vue'
import Vuex from 'vuex'
import authorization from './modules/authorization'
Vue.use(Vuex)
export const store = new Vuex.Store({
modules: {
authorization
}
})
So far all I have done is create a js file which exports a function returning the AUTH_STATE property of authorization state variable.
A component for testing:
<template lang="html">
<label class="login-label" for="username">Username
<input class="login-input-field" type="text" name="username" v-model="username">
</label>
<label class="login-label" for="password" style="margin-top">Password
<input class="login-input-field" type="password" name="username" v-model="password">
</label>
<button class="login-submit-btn primary-green-bg" type="button" #click="login(username, password)">Login</button>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import * as NameSpace from '../../store/NameSpace'
import { getAuth } from '../../Services/test'
export default {
data () {
return {
username: '',
password: ''
}
},
computed: {
...mapGetters({
authStateObject: NameSpace.AUTH_GETTER
}),
authState () {
return this.authStateObject.auth
},
authError () {
return this.authStateObject.error
}
},
watch: {
authError () {
console.log('watch: ', getAuth()) // ------------------------- [3]
}
},
authState () {
if (this.authState.sessionToken) {
console.log('watch: ', getAuth()) // ------------------------- [2]
}
},
methods: {
...mapActions({
authorize: NameSpace.ASYNC_AUTH_ACTION
}),
login (username, password) {
this.authorize({username, password})
console.log(getAuth()) // ---------------------------[1]
}
}
}
</script>
On the button click default state is logged on to the console. The action in my case results in an api call, resulting a state change if the username - password combination had a record.
A success case results in showing the console in authState watch, the imported function can print the changes made to the state.
Likewise, on a fail case, the watch on authError will show the changes made to the state
For anyone wondering how to access a mutation from a javascript file, you can do the following:
import store from './store'
store.commit('mutation_name', mutation_argument);
Or for actions,
store.dispatch('action_name', action_argument)
import store from './store'
and than
store.commit('mutation_name', mutation_argument)
if you use js file
You can also access actions like:
import store from './store'
store.dispatch('action_name', action_argument)

Categories

Resources