why is vuex store only available after setTimeout? - javascript

I have a lib which imports the vuex store
import {store} from "./index"
and that index file has an constant export like
export const store = new Vuex.Store({ ...
in the file I'm doing the import, I wanted to use something from the store after the import, but store was undefined.
if I wrapped my store access in a setTimeout like
setTimeout(()=>{
// use store normally now..
},0)
it works.
Why? I'm guessing this isn't specific to Vuex but I don't know why it's happening.

This is probably a case of circular dependency.
Circular dependencies compile in webpack, but you get bugs at runtime.
Assuming you have files A and B and the dep chain is like A -> B -> A, then when B tries to import A, it still hasn't got to export stuff (because import statements precede statements that aren't import statements).
So import default ./A from B immediately returns undefined.
So either: make the B export a function that is somehow called after the A export is called, or create a module C that both A and B depend on that somehow fixes this circular dependency.

I'm guessing you are loading things out of order or your setup is a little off.
I would try injecting the store into your Vue instance, then you can assume it will be available in all sub components.
main.js
import {store} from "./index"
new Vue({
el: '#app',
store,
render: h => h(App)
})
Now in any child components you will have access to your store via this.$store

Related

Is there a way to encapsulate Vuex store inside Vue plugin (its install function)?

I have a plugin and this plugin uses Vuex
// plugin.js
import Vuex from "vuex";
import store from "./store.js";
export default {
install(Vue, options) {
const storeInstance = new Vuex.Store(store);
Vue.prototype.$store = storeInstance;
}
};
And in that plugin I import a store object.
// store.js
export default {
actions: {
SOME_RANDOM_ACTION({ state, commit }) {
console.log("some random action");
}
}
};
Dispatching actions and using state is fine and works as expected.
But when I add this plugin to another Vue instance that uses vuex, store object re-initializes with new state.
// index.js
import Vue from "vue";
import Vuex from "vuex";
import App from "./App.vue";
import plugin from "./plugin.js";
Vue.use(Vuex);
Vue.use(plugin);
new Vue({
// WARN when i uncomment this next line of code Vuex gets re-initialized with new object
// store: new Vuex.Store({ state: { hello: "hix" } }),
components: {
App
}
}).$mount("#app");
When you uncomment store initialization, store that was defined in the plugin is now not available.
Currently, I have these solutions in mind:
Export my plugin store object to index.js main app, and use this store as a module.
Use some other state management.
Is there a way to use Vuex inside my plugin?
https://codesandbox.io/s/vibrant-sanne-67yej?file=/src/main.js:0-371
Vuex plugin uses store option to assign store instance to Vue.prototype.$store, similarly to your own plugin.
If the intention is to use multiple stores, their names shouldn't collide. The key is to name your store object inside plugin something other than $store
Vue.prototype.$myPluginStore = storeInstance;
But this still doesn't encapsulate $myPluginStore inside the plugin, as it is accessible within the app.
// App.vue
computed: {
appState() {
return this.$store.state;
},
pluginState() {
return this.$myPluginStore.state; // this is now accessible within the main app
}
}
It would be a reasonable solution to allow a store to be used as a module of existing store instead of creating a new store, but only when used within one app and not when used as a plugin for a package.
The main problem is that default store instance ($store) can make use of Vuex helpers - mapGetters, etc.
You can take advantage of the install method exposed by the plugin to get access to the store - which should be accessible from your other component.
One possible solution is to register your store in the index.js like:
import Vue from "vue";
import App from "./App.vue";
import store from "./store";
import plugin from "./plugin";
Vue.use(plugin);
new Vue({
store,
components: {
App
}
}).$mount("#app");
You can then expose $doStuff() and get access to $store in the plugin.js
export default {
install(Vue) {
Vue.prototype.$doStuff = function (payload) {
this.$store.dispatch("SOME_RANDOM_ACTION", payload);
};
}
};
The store instance is accessible from your plugin or all the other components.
You can see a working sample here

Vue - Import npm package that has no exports

I am trying to import an npm package into a Vue component.
The package (JSPrintManager - here) is just a JavaScript file with no exports. Consequently, I cannot use:
import { JSPM } from "jsprintmanager"
I have also had no success with the following:
import JSPM from "jsprintmanager"
import * as JSPM from "../../node_modules/jsprintmanager/JSPrintManager"
import * as JSPM from "../../node_modules/jsprintmanager/JSPrintManager.js"
Am I barking up the wrong tree?
If there is no way to import an npm package that is not a module, is there another way to load the relevant JavaScript file (currently residing in my node-modules directory) into my component?
I am using the Vue CLI
jspm.plugin.js
import * from '../../node_modules/jsprintmanager/JSPrintManager';
export default {
install(Vue) {
Vue.prototype.$JSPM = window.JSPM
}
}
main.js
import Vue from 'vue'
import JSPM from './jspm.plugin';
Vue.use(JSPM);
In any of your components you can now access JSPM as this.$JSPM
If you want to use it outside of your components (say, in store) and you want it to be the same instance as the one Vue uses, export it from Vue, in main.js
const Instance = new Vue({
...whatever you have here..
}).$mount('#app');
export const { $JSPM } = Instance
Now you can import { $JSPM } from '#/main' anywhere.
That would be the Vue way. Now, in all fairness, the fact your import is run for the side effect of attaching something to the window object which you then inject into Vue is not very Vue-ish. So the quick and dirty way to do it would be, in your component:
import * from '../../node_modules/jsprintmanager/JSPrintManager';
export default {
data: () => ({
JSPM: null
}),
mounted() {
this.JSPM = window.JSPM;
// this.JSPM is available in any of your methods
// after mount, obviously
}
}
The main point of the above "simpler" method is that you have to make the assignment after the page finished loading and running the JSPM code (and window.JSPM has been populated).
Obviously, if you disover it sometimes fails (due to size, poor connection or poor hosting), you might want to check window.JSPM for truthiness and, if not there yet, call the assignment function again after in a few seconds until it succeeds or until it reaches the max number of tries you set for it.

Add global variable in Vue.js 3

Anybody know how to do add a global variable in Vue 3 ?
in Vue 2 we use this in the main.js file:
Vue.prototype.$myGlobalVariable = globalVariable
The most direct replacement is app.config.globalProperties. See:
https://vuejs.org/api/application.html#app-config-globalproperties
So:
Vue.prototype.$myGlobalVariable = globalVariable
becomes:
const app = createApp(RootComponent)
app.config.globalProperties.$myGlobalVariable = globalVariable
This is scoped to a particular application rather than being global as it was with Vue.prototype. This is by design, all 'global' configuration options are now scoped to an application.
The relevant RFC is here:
https://github.com/vuejs/rfcs/blob/master/active-rfcs/0009-global-api-change.md
Properties added to globalProperties will be available via the component instance for all components within the application. So if you're using the Options API you'll be able to access them using this.$myGlobalVariable, just like you could with Vue.prototype. They'll also be available in the template without the this., e.g. {{ $myGlobalVariable }}.
If you're using the Composition API then you'll still be able to use these properties within the template, but you won't have access to the component instance within setup, so these properties won't be accessible there.
While hacks involving getCurrentInstance() can be used to access globalProperties within setup, those hacks involve using undocumented APIs and are not the recommended approach.
Instead, application-level provide/inject (also discussed in that RFC) can be used as an alternative to Vue.prototype:
const app = createApp(RootComponent)
app.provide('myGlobalVariable', globalVariable)
In the descendant component this can then be accessed using inject. e.g. With <script setup>:
<script setup>
import { inject } from 'vue'
const myGlobalVariable = inject('myGlobalVariable')
</script>
Or with an explicit setup function:
import { inject } from 'vue'
export default {
setup() {
const myGlobalVariable = inject('myGlobalVariable')
// Expose it to the template, if required
return {
myGlobalVariable
}
}
}
Or with the Options API:
export default {
inject: ['myGlobalVariable']
}
Docs: https://vuejs.org/api/application.html#app-provide
The idea here is that the component can explicitly declare the property rather than inheriting it by magic. That avoids problems like name collisions, so there's no need to use a $ prefix. It can also help to make it clearer where exactly a property is coming from.
It is common for the inject function to be wrapped in a composable. For example, the useRoute composable exposed by Vue Router is just a wrapper around inject.
In addition to globalProperties and provide/inject, there are various other techniques that might be used to solve the same problems as Vue.prototype. For example, ES modules, stores, or even global mixins. These aren't necessarily direct answers to the specific question posted here, but I've gone into more detail describing the various approaches at:
https://skirtles-code.github.io/vue-examples/patterns/global-properties.html
Which approach you prefer will depend on your circumstances.
How to add a global variable using Vue 3 and vue-cli (or Vite)
Note: You can drop the dollar sign from your $globalVariable and just use globalVariable, just like in the documentation.
Initially your main.js file looks something like this (adding router for common use case):
import { createApp } from 'vue'
import { App } from './App.vue'
import { router } from './router'
createApp(App).use(router).mount('#app')
To use add the global variable using Vue 3 and the vue-cli or Vite:
import { createApp } from 'vue'
import { App } from './App.vue'
import { router } from './router'
// 1. Assign app to a variable
let app = createApp(App)
// 2. Assign the global variable before mounting
app.config.globalProperties.globalVar = 'globalVar'
// 3. Use router and mount app
app.use(router).mount('#app')
Then to access the variables in components like this:
<script>
export default {
data() {
return {
myVar: this.globalVar
}
}
}
</script>
like in the template like this:
<template>
<h1>{{ globalVar }}</h1>
</template>
And that's it. Happy coding!
About Global Variables and Composition API
According to the very bottom of samayo's answer on this post, global variables are only available on the Options API.
Quoting the bottom of his answer:
Note: This is only for the Options API. Evan You (Vue creator) says: "config.globalProperties are meant as an escape hatch for replicating the behavior of Vue.prototype. In setup functions, simply import what you need or explicitly use provide/inject to expose properties to app.
I recommend to use provide/inject approach as follows :
in main.js :
import {createApp} from 'vue'
let app=createApp({
provide:{
globalVariable:123
}
}).$mount('#app')
in some child or grand-child component do :
export default{
name:'some-compo',
inject:['globalVariable'],
//then access this.globalVariable as property in you component
...
}
for composition api and script setup :
import { inject } from 'vue'
let globalVar=inject('globalVariable')
If possible you should use imports or provide/inject. Another way to define global variables/functions and use them would be using globalProperties (although this seems to be considered more of an anti-pattern). But if a library you use uses globalProperties then you can use it like this. This also works with global functions.
const app = Vue.createApp({})
app.config.globalProperties.$http = () => {} // global function
app.config.globalProperties.$globalVariable = 'Jimmy' // global variable
1. Using options API
mounted() {
console.log(this.$globalVariable)
}
2. Using setup method
<script setup>
import { getCurrentInstance } from 'vue'
const app = getCurrentInstance()
const progressBar = app.appContext.config.globalProperties.$globalVariable
console.log(this.$globalVariable)
</script>
For those of you who are confused about how to access globalProperties in the setup() method, you can use getCurrentInstance() as in the following documentation.
https://v3.vuejs.org/api/composition-api.html#getcurrentinstance
In my case I had to create a global var and get the data from a script.
Used provide and inject:
In main.js:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App);
app.provide('message',document.querySelector('script[name="nameSCRIPT"]').innerHTML.split('=').slice(1).join('=').slice(1,-1));
app.mount('#app')
In index.html:
<script name="nameSCRIPT">nameSCRIPT="HELLO"</script>
In child component:
inject:['message'],
mounted(){
console.log(this.message)
},

Vuex - rawModule is undefined moving from single to multiple modules

Moved my project from single module in vuex store to multiple following the documentation.
It states that the specific module should be accessed like so:
store.state.a // -> `moduleA`'s state
This is when accessing the state of a module. It fails to say how to access getters and mutations as well as the commands like 'commit' and 'replaceState' for a specific module so I made my own conclusion:
store.getters.a
store.mutations.a
store.a.commit()
store.a.replaceState()
1) Are those conclusions correct?
2) Using these I get a really general error message:
TypeError: rawModule is undefined
Here is my store.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
listingModule: listingModule,
openListingsOnDashModule: listingsOnDashModule,
closedListingsOnDashModule: listingsOnDashModule
}
})
const listingsOnDashModule = {...}
const listingModule = {...}
// their content hasn't changes since the single module approach.
This error:
TypeError: rawModule is undefined
is caused by trying to register undefined as a module, you are probably importing undefined by accident when registering the module.
As for the syntax store.state.a is correct. With the rest the .a should just be omitted unless you are explicitly defining a namespace.

how do I access `this.` in exported in vuejs

I have some files that return simple data, like mutation.js for vuex but generally they are just like this:
export default {
...
someFunction() {}
...
}
Right now, I would like to access this. so I can use the vue-i18n translation like this.$t('TRANS_TOKEN') but for some reason I am not able to use this. I am thinking about including vue in this file as: import vue from 'vue' and probably do vue.$t(..) if it works but I tried it and it doesn't
First a question. Why doing translations in mutations file? I'd keep translations in your components only.
You can however achieve what you want, by doing so
// i18n.js
const i18n = new VueI18n();
export default i18n;
// main.js
import VueI18n from 'vue-i18n';
import i18n from './i18n.js';
Vue.use(VueI18n);
new Vue({
i18n,
...
});
// Anywhere else, grab the i18n instance to do translations
import i18n from './i18n.js';
i18n.t('translate this');
Documentation on all the methods available on the VueI18n instance.

Categories

Resources