How to use shorter path to get vuex contents? - vue.js

In my vuex /store/state.js I have an export default that looks like this:
export default {
foo: {
bar:{
tar: {
info: 42
}
}
}
}
So, whenever I want to access info, I usually do in my methods like this;
methods: {
getInfo () {
return this.$store.state.foo.bar.tar.info
}
}
This is just for a demo purpose, and mine is actually a bit worse, but I ended up doing the same so, I tried minimize the code using a computed prop:
computed: {
info () {
return this.$store.state.foo.bar.tar.info
}
}
Now, I just call info but still, not sure if there is a better way to get values, because sometimes I just need to call info only one in a page, so I have to use the full path or create a computed property for it.
Is there any other way to do this

I always separate vuex into separated modules. For instance if you have store for foo module. I will create file named foo.js which contains
const fooModule = {
state: {
foo: {
bar: {
tar: {
info: 42
}
}
}
},
getters: {
info (state) {
return state.foo.bar.tar.info
}
},
mutations: {
setInfo (state, payload) {
state.foo.bar.tar.info = payload
}
},
actions: {
getInfo ({commit}, payload) {
commit('setInfo', payload)
}
}
}
export default fooModule
Then in your main index vuex, import the module like this way
import Vue from 'vue'
import Vuex from 'vuex'
import fooModule from './foo.js'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
fooModule
}
})
export default store
Then if you wanna get info, you just write your code like this
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters([
'getInfo'
])
}
}

#jefry Dewangga has the the right idea but introducing mapGetters is unnecessary.
VueX by default includes namespacing for modules which allows us to load multiple modules into a store and then reference them with or without namespacing them and it'll figure the rest out.
For Instance if we have the structure of
|-- store
|-- Modules
|-- module1.js
|-- module2.js
|-- module3.js
|-- index.js
We can use index in such a way to bind all of our modules into our Vuex store doing the following:
import Vue from 'vue'
import Vuex from 'vuex'
import modules from './modules'
Vue.use(Vuex)
export default new Vuex.Store({
modules
})
An example of our module1 could be:
const state = {
LoggedIn: true
}
const mutations = {
LOGIN(state) {
state.LoggedIn = true;
},
LOGOFF(state) {
state.LoggedIn = false;
}
}
const actions = {}
export default {
state,
mutations,
actions
}
This in turn gives us the ability to say:
this.$store.commit('LOGIN');
Note that we haven't used any namespacing but as we haven't included any duplicate mutations from within our modules were absolutely fine to implicitly declare this.
Now if we want to use namespacing we can do the following which will explicitly use out module:
this.$store.module1.commit('LOGIN');
MapGetters are useful but they provide a lot of extra overhead when we can neatly digest out modules without having to continuously map everything, unless well we find the mapping useful. A great example of when MapGetters become handy is when we are working many components down in a large project and we want to be able to look at our source without having to necessarily worry about the frontend implementation.

Related

vuex state not updating UI on change

I am in the process of converting a vue2 to vue3 project and noticed my UI isn't updating when objects from my vuex store are updating. Here is how I create my store:
store/index.js
import {mutations} from './mutations';
import {createStore} from 'vuex'
export default createStore({
state() {
return {
...
}
},
mutations
});
mutations.js
export const mutations = {
narrative(state, v) {
state.narrative = v;
}
};
app.js
import { createApp } from 'vue';
import store from './store/index';
const app = createApp({
mixins: [
require('./mixins/base')
]
}).use(store)
So when I mutate one of the vuex objects, I write to the console log immediately and see the data has changed
let narrative = _.find(this.$store.state.narratives, ['id', this.selectedNarrativeId]);
if (narrative) {
console.log(narrative.id); // PRINTS CORRECT UPDATED ID
this.$store.commit('narrative', narrative);
console.log(this.$store.state.narrative.id); // PRINTS CORRECT UPDATED ID
}
But the UI does not change. However, if I used a computed property, the UI updates immediately. What am I doing wrong with the vuex store?
computed: {
currentNarrative() {
let narrative = _.find(this.$store.state.narratives, ['id', this.selectedNarrativeId]);
if (narrative) {
return narrative;
}
return {};
},
}
Versions
vue 3.2.33
vuex 4.0.2
Replacing the require by an import + rebooting the machine fixed the issue, maybe some broken was still running on the server.

Correct setup and use of VUEX store mutation-types

I'm developing a Chrome extension using one of the Vue js boilerplates from Github. The default boilerplate setup is as follows:
store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import mutations from './mutations';
import * as actions from './actions'; // all actions are imported as separate vars
Vue.use(Vuex);
export default new Vuex.Store({
state: { },
mutations,
actions
});
Then in actions.js
import * as types from './mutation-types';
export const setFoo = ({ commit }, payload) => {
commit(types.SET_FOO, payload); // SET_FOO is defined in the mutation-types file
};
I think the above approach lacks a fundamental reason why we want to use mutation types file - to avoid retyping the names for mutations and actions.
So instead, I came up with a different approach:
store/index.js
...
import actions from './actions'; // actions are imported as separate functions
...
Then in actions.js
import * as types from './mutation-types';
export default {
[types.UPDATE_FOO] ({commit}, payload) {
commit(types.UPDATE_FOO, payload);
}
}
Then anywhere in the extension, we could also import mutation-types and dispatch actions using const names like so:
store.dispatch(types.UPDATE_FOO, 'some value');
The second approach seems to be more practical in terms of naming and then dispatching/committing our actions/mutations. Or could there be any issues with the latest?
Which of the above, would be generally better practice?
The first approach is preferable, but it's completely up to you. Similar approach is used in official Vuex docs.
Refrence
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
// actions.js
actions: {
checkout ({ commit, state }, products) {
// save the items currently in the cart
const savedCartItems = [...state.cart.added]
// send out checkout request, and optimistically
// clear the cart
commit(types.CHECKOUT_REQUEST)
// the shop API accepts a success callback and a failure callback
shop.buyProducts(
products,
// handle success
() => commit(types.CHECKOUT_SUCCESS),
// handle failure
() => commit(types.CHECKOUT_FAILURE, savedCartItems)
)
}
}

Understanding State and Getters in Nuxt.js: Getters won't working

i'm new to Vue and Nuxt and i'm building my first website in Universal mode with these framework.
I'm a bit confused on how the store works in nuxt, since following the official documentation i can't achieve what i have in mind.
In my store folder i have placed for now only one file called "products.js", in there i export the state like this:
export const state = () => ({
mistica: {
id: 1,
name: 'mistica'
}
})
(The object is simplified in order to provide a cleaner explanation)
In the same file i set up a simple getter, for example:
export const getters = () => ({
getName: (state) => {
return state.mistica.name
}
})
Now, according to the documentation, in the component i set up like this:
computed: {
getName () {
return this.$store.getters['products/getName']
}
}
or either (don't know what to use):
computed: {
getName () {
return this.$store.getters.products.getName
}
}
but when using "getName" in template is "undefined", in the latter case the app is broken and it says "Cannot read property 'getName' of undefined"
Note that in the template i can access directly the state value with "$store.state.products.mistica.name" with no problems, why so?
What am i doing wrong, or better, what didn't i understand?
Using factory function for a state is a nuxt.js feature. It is used in the SSR mode to create a new state for each client. But for getters it doesn't make sense, because these are pure functions of the state. getters should be a plain object:
export const getters = {
getName: (state) => {
return state.mistica.name
}
}
After this change getters should work.
Then you can use the this.$store.getters['products/getName'] in your components.
You can't use this.$store.getters.products.getName, as this is the incorrect syntax.
But to get simpler and more clean code, you can use the mapGetters helper from the vuex:
import { mapGetters } from "vuex";
...
computed: {
...mapGetters("products", [
"getName",
// Here you can import other getters from the products.js
])
}
Couple of things. In your "store" folder you might need an index.js for nuxt to set a root module. This is the only module you can use nuxtServerInit in also and that can be very handy.
In your products.js you are part of the way there. Your state should be exported as a function but actions, mutations and getters are just objects. So change your getters to this:
export const getters = {
getName: state => {
return state.mistica.name
}
}
Then your second computed should get the getter. I usually prefer to use "mapGetters" which you can implement in a page/component like this:
<script>
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters({
getName: 'products/getName'
})
}
</script>
Then you can use getName in your template with {{ getName }} or in your script with this.getName.

Vuex: createNamespacedHelpers with dynamic namespace

In almost all guides, tutorial, posts, etc that I have seen on vuex module registration, if the module is registered by the component the createNamespacedHelpers are imported and defined prior to the export default component statement, e.g.:
import {createNamespacedHelpers} from 'vuex'
const {mapState} = createNamespacedHelpers('mymod')
import module from '#/store/modules/mymod'
export default {
beforeCreated() {
this.$store.registerModule('mymod', module)
}
}
this works as expected, but what if we want the module to have a unique or user defined namespace?
import {createNamespacedHelpers} from 'vuex'
import module from '#/store/modules/mymod'
export default {
props: { namespace: 'mymod' },
beforeCreated() {
const ns = this.$options.propData.namespace
this.$store.registerModule(ns, module)
const {mapState} = createNamespacedHelpers(ns)
this.$options.computed = {
...mapState(['testVar'])
}
}
}
I thought this would work, but it doesnt.
Why is something like this needed?
because
export default {
...
computed: {
...mapState(this.namespace, ['testVar']),
...
},
...
}
doesnt work
This style of work around by utilising beforeCreate to access the variables you want should work, I did this from the props passed into your component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above
}
}
Hope you find this useful.
All posted answers are just workarounds leading to a code that feels verbose and way away from standard code people are used to when dealing with stores.
So I just wanted to let everyone know that brophdawg11 (one of the commenters on the issue #863) created (and open sourced) set of mapInstanceXXX helpers aiming to solve this issue.
There is also series of 3 blog posts explaining reasons behind. Good read...
I found this from veux github issue, it seems to meet your needs
https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
{
props: ['namespace'],
computed: mapState({
state (state) {
return state[this.namespace]
},
someGetter (state, getters) {
return getters[this.namespace + '/someGetter']
}
}),
methods: {
...mapActions({
someAction (dispatch, payload) {
return dispatch(this.namespace + '/someAction', payload)
}
}),
...mapMutations({
someMutation (commit, payload) {
return commit(this.namespace + '/someMutation', payload)
})
})
}
}
... or maybe we don't need mapXXX helpers,
mentioned by this comment https://github.com/vuejs/vuex/issues/863#issuecomment-439039257
computed: {
state () {
return this.$store.state[this.namespace]
},
someGetter () {
return this.$store.getters[this.namespace + '/someGetter']
}
},

access store from outside js file in nuxtjs

I'm trying to access the store from a file outside a component
When I search for this problem I saw that people say that I should import the store from my file and then I can get access to it, but I can't make this work
my store is built like this:
const createStore = () => {
return new Vuex.Store({
state: { ... },
getters: { ... },
mutations: { ... },
actions: { ... },
})
}
and I've tried to import it in my js file like I saw recommended
import store from '#/store'
Any ideas?
You could import the store by registering it upon instantiation, like this:
External file some-service.js:
export default class SomeService {
constructor(store) {
this.store = store
}
doStuff() {
this.store.dispatch('someAction')
}
}