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.
Related
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.
Lets say I have 5 named routes, Index, News, TaggedNews, NewsItem, TaggedNewsItem
To change between various routes I call router.push and it is working properly
When I am at the NewsItem or TaggedNewsItem I want to go back to the last non item url, how do I do that
Take an example history stack like this
Index
News
News
TaggedNews
TaggedNewsItem
TaggedNewsItem
when I lclick a button I want to go from the last TaggedNewsItem by N steps where N will take me to the last named route TaggedNews or News or Index whichever comes first
how do I do this using Vue Router
It gives the option go(N) where N is the number of steps, problem is I need to find N to see where the last Index or News or TaggedNews item is present
I don't think the vue router saves the history stack. You would need to save it yourself.
You can use vuex to keep track of the history stack and calculate the last different route path, and have it available everywhere you need.
An implementation could be something like:
history.js
const history = {
namespaced: true,
state: {
stack: [],
},
mutations: {
PUSH_STACK(state, routeName) => state.stack.push(routeName),
},
actions: {
pushStack({ commit }, routeName) => commit('PUSH_STACK', routeName),
},
getters: {
lastDiff(state) {
const reversed = state.stack.slice().reverse();
const diff = reversed.findIndex(route => route !== reversed[0]);
return -1 * diff;
}
}
}
export default { history }
store.js
import Vue from 'vue'
import Vuex from 'vuex'
import { history } from './history';
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
history
}
})
export default { store }
Include it in your main.js file:
import Vue from 'vue';
import { store } from './store';
const app = new Vue({
el: '#app',
store
});
In your router file, you can add a global after hook to push the path name to the store like this:
import { store } from './store';
const router = // router config
router.afterEach((to) => store.dispatch('history/pushStack', to.name));
And in your components you can use it like this:
const backN = this.$store.getters['history/lastDiff'];
this.$router.go(backN);
I'm trying to spilt up my store firstly so all the getters, actions and mutations are in separate files as per this answer: https://stackoverflow.com/a/50306081/5434053
I have API calls in a services file, the POST api calls seem to work but the GET ones do not, the action seems to get nothing back. Have I missed something?
import Vue from 'vue';
import Vuex from 'vuex';
import actions from './actions';
import getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
status: '',
token: localStorage.getItem('token') || '',
user: {},
movie: {},
movies: {}
},
actions,
getters,
mutations,
});
store/actions.js
import axios from 'axios'
import {APIService} from '../services/APIService';
const apiService = new APIService();
let getMovies = async ({commit, state, getter}) => {
try {
await apiService.getMovies(localStorage.getItem('token')).then((data, error) => {
for (const movie of data.data.data.movies) {
movie.edit = false;
movie.deleted = false;
}
this.movies = data.data.data.movies;
console.log(data)
commit("fetch_movies", this.movies);
})
} catch(error) {
commit('auth_error')
localStorage.removeItem('token')
console.log(error)
}
}
export default {
getMovies,
};
It looks like you are importing actions from ./actions. However, when I look at your file at store/actions.js you are not exporting anything.
For JavaScript modules to work you have to add export statements to your files - so you can import the exported variables/properties somewhere else.
Also: You seem to only declare the function getMovies() without adding it to an actions object (which you import in store.js).
Try this:
// in store/actions.js
// your code..
const actions = {
getMovies,
}
export default actions;
Edit:
Just noticed you also use this in your action. If I am not mistaken it should be undefined as you only work with lambdas (Arrow Functions).
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)
)
}
}
I'm trying to write a simple plugin for my Vue.js(Nuxt) project. I came across this post Adding Mutations to Vuex store as part of Vue Plugin but still unable to get it working.
Here is my application structure.
~ is root
~/plugins/HTTP/index.js
~/plugins/HTTP/_store/ => index.js, actions.js, getters.js, mutations.js
~/plugins/HTTP/_api/ => index.js
**Global Store**
~/store/index.js
~/store/modules/
~/store/modules/testing => index.js, actions.js, getters.js, mutations.js
in my ~/plugins/HTTP/index.js, I have the following code
import Vue from 'vue';
import store from '~/store';
const HTTP = {
install(vue, { store }){ // Now you plugin depend on store
if(!store){
throw new Error('Please provide vuex plugin.')
}
// register your own vuex module
store.registerModule({store})
}
}
export default HTTP;
Vue.use(HTTP)
In my ~/store/index.js I have the following code:
import Vuex from 'vuex'
import testingModule from './modules/testing'
const state = () => {
return new Vuex.Store({
modules:{
testing: testingModule
}
})
}
export default state
When I try to run it, it gives me the following message:
Cannot destructure property `store` of 'undefined' or 'null'.
What did I do wrong here?
You aren't passing any properties so the error is correct. You need pass in an options object when you tell it to use. It can be empty, but it needs an object.
import Vue from 'vue';
import store from '~/store';
const HTTP = {
install(vue, { store }){ // Now you plugin depend on store
if(!store){
throw new Error('Please provide vuex plugin.')
}
// register your own vuex module
store.registerModule({store})
}
}
export default HTTP;
Vue.use(HTTP, {}) // <---------- Empty object to avoid allow destructuring.