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.
Related
I am using Composition API in Vue2. Can you tell me how to access mapState with composition API? I want to watch for state changes as well. Hence I would have to use it within setup function as well (not only in return). Thanks
The Vuex map helpers aren't supported (yet?) in the Vue 2 or Vue 3 composition API, and this proposal for them has been stalled for a while.
You'll have to manually create a computed like in the docs:
const item = computed(() => store.state.item);
A more complete example:
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
const item = computed(() => store.state.item);
return {
item
};
}
}
For me the trick was using the vuex-composition-helper npm package.
https://www.npmjs.com/package/vuex-composition-helpers
import { useState, useActions } from 'vuex-composition-helpers';
export default {
props: {
articleId: String
},
setup(props) {
const { fetch } = useActions(['fetch']);
const { article, comments } = useState(['article', 'comments']);
fetch(props.articleId); // dispatch the "fetch" action
return {
// both are computed compositions for to the store
article,
comments
}
}
}
On component mount(), Axios fetches information from the back end. On a production site, where the user is going back and forth between routes it would be inefficient to make the same call again and again when the data is already in state.
How do the pros design their VueJS apps so that unnecessary Axios calls are not made?
Thank you,
If the data is central to your application and being stored in Vuex (assuming that's what you mean by "state"), why not just load it where you initialise your store?
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'wherever'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
centralData: {}
},
mutations: {
setCentralData (state, centralData) {
state.centralData = centralData
}
},
actions: {
async loadCentralData ({ commit }) {
const { data } = await axios.get('/backend')
commit('setCentralData', data)
}
}
}
// initialise
export const init = store.dispatch('loadCentralData')
export default store
If you need to wait for the dispatch to complete before (for example) mounting your root Vue instance, you can use the init promise
import Vue from 'vue'
import router from 'path/to/router'
import store, { init } from 'path/to/store'
init.then(() => {
new Vue({
store,
router,
// etc
}).$mount('#app')
})
You can import and use the init promise anywhere in order to wait for the data to load.
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)
)
}
}
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.
Early today try vuex 2.1.2 vue:2.1.0, the directory structure is as follows
store.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import common from './common/store'
export default new Vuex.Store({
modules : {
common
}
})
mutations-types.js:
export const ADD_NUM = "ADD_NUM"
common/store.js:
import * as types from '../mutations-types'
const state = {
num : 1
}
const mutations = {
[types.ADD_NUM] : function(state){
state.num = state.num + 1;
},
}
export default {
state,
mutations
}
common/actions.js:
import * as types from '../mutations-types'
export default {
setNum : store => {
store.dispatch(types.ADD_NUM)
},
}
common/getters.js:
export default {
getNum : state => {
state.common.num
},
}
Then get the value of num in Hello.vue through getters
In the vue entry file main.js, a store is injected
run error:
Property or method "getNum" is not defined on the instance but
referenced during render
Why is this error reported? Does this directory structure and code correct?
First of all, I did not read the official documents
Using mapGetters solves the problem
THANKS #Potray
I refer to the official demo link