I'm using Vuex to try manage application-level data. I fetch the data, using axios, then propogate that data to data variables in a component. Nothing complicated.
My store looks like this
// store.js
// appropriate imports and such
export const store = new Vuex.Store({
state: {
var: []
},
actions:{
getData(context){
axios.get('/endpoint').then(function(response){
context.state.var = res.data.value1;
console.log("action in store.js run");
//appropriate brackets,etc below
I then dispatch this action in my app.js
//appropriate imports and such above
const app = new Vue({
el: '#app',
store: store,
created() {
this.$store.dispatch('getRightSidebar')
console.log("action dispatched")
}
});
I use the created lifecycle hook to ensure this action gets dispatched before the component is mounted. Surely, at this point two messages should be logged to the console. One message from the created lifestyle hook and another from the actual action being dispatched. However, when I run the application only the former message is logged. Surely, when the action is dispacthed, the actual method/request me be called/executed.
Now, when I print the values of the state variables, from within the mounted lifecycle hook of the component, they're undefined. However, if I print the state it logs the object with the appropriate data
///component.js
mounted() {
console.log("component mounted")
console.log(this.$store.state.var) // undefined
console.log(this.$store.state) // Obeject with the appropriate data
}
So on one hand it seems to dispatch the action, but when I try to access individual objects from the state, it craps itself. Is there something wrong with the way I'm trying to access objects within the state?
you need to "wait" for getData promise to resolve
when created() hook runs there can be no data
export const store = new Vuex.Store({
state: {
var: []
},
actions:{
getRightSidebar(context){
// must return Promise to subscribe to it!
return axios.get('/endpoint').then(function(response){
context.state.var = res.data.value1;
console.log("action in store.js run");
const app = new Vue({
el: '#app',
store: store,
data: {
isLoading: true
},
async mounted() {
await this.$store.dispatch('getRightSidebar')
// after this there is gonna be some data
this.isLoading = false
}
})
<template>
<div>
<div v-if='isLoading'>loading...</div>
<div v-else>{{$store.state.yourvariable}}</div>
</div>
</template>
Related
I have an array. I take data for it via rest api. I can call a mutation getData() from any component, but I need it to be automatically called when an object Vuex.Store is created, can I do this?
export default new Vuex.Store({
state: {
myArray: [],
},
mutations: {
getData() {
//get data from remote API and pass to myArray
axios.post('').then(response => {
this.myArray = response.data;
};
}
}
})
First things first: Mutations are synchronous pure functions. This means that your mutations should not have side-effects, and at the end of your mutation the state of your store should be updated to the new state. Axios uses promises and is thus asynchronous. You should do this in an action!
As for automatically executing a data fetch, you can either do this in the file where you define your store, or in your Vue entry point (e.g. App.vue) in a lifecycle hook. Keep in mind though that your axios call is asynchronous, which means that your app will load while data is loading in the background. You have to handle this case somehow.
// store.js
import Vue from 'vue';
import Vuex from 'vuex';
const store = new Vuex.Store({
state: {
myArray: [],
},
mutations: {
setMyArray(state, payload) {
Vue.set(state, 'myArray', payload);
},
},
actions: {
fetchData({ commit }) {
axios.post('').then(response => {
commit('setMyArray', response.data);
};
}
}
});
// Setup
Vue.use(Vuex);
// Now that we have a store, we can literally just call actions like we normally would within Vue
store.dispatch('fetchData');
// Keep in mind that action is not blocking execution. Execution will continue while data is fetching in the background
I need to get data from an endpoint, and then assign the data to state variables in the store.
The code looks something like this.
import Vue from 'vue'
import axios from 'axios'
import Vuex from 'vuex'
Vue.use(Vuex)
export const store = new Vuex.Store({
state: {
sample: 'foo',
sample2 : [],
sample3: []
},
getters:{
},
actions:{
getData(context){
axios.get('/endpoint').then(function(response){
console.log(context.state);
console.log(context.state.sample);
console.log(response);
context.state.sample = 'bar';
console.log(context.state.sample);
context.state.sample2 = response.data.sample2data;
context.state.sample3 = response.data.sample3data;
}
);
}
},
mutations:{
}
});
The trouble is the application doesn't execute the axios request at all, as far as I can tell. I've tested the endpoint elsewhere and I'm certain there's nothing wrong with the request itself. Surely, everytime my application is mounted the request should be executed?
In the mounted method of your App.js you can do
mounted() {
this.$store.dispatch('getData')
}
You need to dispatch the action in the created method, where you create your vue instance. If the action is dispatched in a component, the store is only injected after the component is mounted. The data will only load after mounting the component which means component(s) that need to read the data, will have access to it, however the data in the state will be undefined on load.
export const store = new Vuex.Store({
state: {
sample2 : [], // undefined
sample3: [] // undefined
},
getters:{
},
actions:{
getData(context){
axios.get('/endpoint').then(function(response){
context.state.sample2 = response.data.sample2data;
context.state.sample3 = response.data.sample3data;
}
);
}
},
mutations:{
}
});
The below ensures that action is dispatched before mounting any components, and therefore the components will be able to read the state after the action has properly set the values
import {store} from 'path/to/store/';
new Vue({
el: '#app',
computed:,
store: store,
created() {
this.$store.dispatch('getData') // dispatch loading
}
})
I have a component which makes a call to my backend API. This then provides me with data that I use for the component. I now want to create another component which also uses that data. While I could just do another api call that seems wasteful.
So, in Profile.vue i have this in the created() function.
<script>
import axios from 'axios';
import { bus } from '../main';
export default {
name: 'Profile',
data() {
return {
loading: false,
error: null,
profileData: null,
getImageUrl: function(id) {
return `http://ddragon.leagueoflegends.com/cdn/9.16.1/img/profileicon/` + id + `.png`;
}
}
},
beforeCreate() {
//Add OR Remove classes and images etc..
},
async created() {
//Once page is loaded do this
this.loading = true;
try {
const response = await axios.get(`/api/profile/${this.$route.params.platform}/${this.$route.params.name}`);
this.profileData = response.data;
this.loading = false;
bus.$emit('profileData', this.profileData)
} catch (error) {
this.loading = false;
this.error = error.response.data.message;
}
}
};
</script>
I then have another child component that I've hooked up using the Vue router, this is to display further information.
MatchHistory compontent
<template>
<section>
<h1>{{profileDatas.profileDatas}}</h1>
</section>
</template>
<script>
import { bus } from '../main';
export default {
name: 'MatchHistory',
data() {
return {
profileDatas: null
}
},
beforeCreate() {
//Add OR Remove classes and images etc..
},
async created() {
bus.$on('profileData', obj => {
this.profileDatas = obj;
});
}
};
</script>
So, I want to take the info and display the data that I have transferred across.
My assumption is based on the fact that these components are defined for two separate routes and an event bus may not work for your situation based on the design of your application. There are several ways to solve this. Two of them listed below.
Vuex (for Vue state management)
Any local storage option - LocalStorage/SessionStorage/IndexDB e.t.c
for more information on VueX, visit https://vuex.vuejs.org/.
for more information on Localstorage, visit https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage.
for more information on session storage, visit https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage
The flow is pretty much the same for any of the options.
Get your data from an API using axios as you did above in Profile.vue
Store the retrieved data with VueX or Local/Session storage
Retrieve the data from Vuex or local/session storage in the created method of MatchHistory.vue component
For the local / session storage options, you will have to convert your object to a json string as only strings can be stored in storage. see below.
in Profile.vue (created)
const response = await axios.get(........)
if(response){
localStorage.setItem('yourstoragekey', JSON.stringify(response));
}
In MatchHistory.Vue (created)
async created() {
var profileData = localStorage.getItem('yourstoragekey')
if(profileData){
profileData = JSON.parse(profileData );
this.profileData = profileData
}
}
You can use vm.$emit to create an Eventbus
// split instance
const EventBus = new Vue({})
class IApp extends Vue {}
IApp.mixin({
beforeCreate: function(){
this.EventBus = EventBus
}
})
const App = new IApp({
created(){
this.EventBus.$on('from-mounted', console.log)
},
mounted(){
this.EventBus.$emit('from-mounted', 'Its a me! Mounted')
}
}).$mount('#app')
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
further readings
You can make use of the VUEX which is a state management system for Vue.
When you make api call and get the data you need, you can COMMIT a MUTATION and pass your data to it. What it will do, it will update your STATE and all of your components will have access to its state (data)
In your async created(), when you get response, just commit mutation to your store in order to update the state. (omitted example here as the vuex store will need configuration before it can perform mutations)
Then in your child component,
data(){
return {
profileDatas: null
}
},
async created() {
this.profileDatas = $store.state.myData;
}
It might seem like an overkill in your case, but this approach is highly beneficial when working with external data that needs to be shared across multiple components
I have a dialog with a v-select that doesn't show values in the drop down after a full page load. It does work after a hot module reload triggered by modifying the component file.
Using vuetify:
"vuetify": "^1.5.17",
Component contains:
template code:
<v-select
:items="routes"
label="Routes"
multiple
chips
persistent-hint
hint="Send to which routes"
v-model="message.routes"
></v-select>
routes is a computed property:
routes: {
get() {
return this.$store.state.routes
}
}
The data gets downloaded in the created event:
created() {
this.downloadRoutes()
}
This maps to a store method that does an AJAX call that commits the returned list:
downloadRoutes({ commit, state }) {
return new Promise((resolve, reject) => {
commit('SET_LOADING', true)
api.get('/route').then(response => {
var routes = response.data.routes
commit('SET_ROUTES', routes)
commit('SET_LOADING', false)
resolve()
})
.catch(function(error) {
commit('SET_LOADING', false)
reject()
})
})
},
AJAX response is just an array of routes:
{"routes":["Route1","Route2","RouteXX"]}
This I have shown by doing a console.log of the response and the state in the computed routes property.
What am I missing?
It sounds like your problem lies somewhere inside the vue instance lifecycle, i.e. you might call this.downloadRoutes() inside the wrong hook. Do your problem still occure if you try with a hardcoded array of Routes?
Found the problem.
My store is initialised via an initialState function.
export default new Vuex.Store({
state: initialState,
getters: {
......
The initial state function declares all the top level collections used in state.
function initialState() {
return {
fullName: localStorage.fullName,
routes: [] // Was missing
}
Adding routes in that initial state solves the problem.
I think it's because watchers on state are not added when new attributes are added to state.
When I do a full page refresh the routes gets added to state after the select is initialized and so it is not watching for changes. During hot module reload the routes is already in the state and is so picked up by the select.
Did anyone tried to use boostrap-vue in combination with vuex?
I'm having hard time calling modals and toast from vuex actions.
Obviously I can not use this from vuex store, therefore I can't use:
this.$bvModal.show('modalId');
I also tried calling modal like this
import Vue from 'vue';
Vue.prototype.$bvModal.show('transaction');
But console gives me following warning:
BootstrapVue warn]: '$bvModal' must be accessed from a Vue instance 'this' context
Any idea how I can call modals and toasts from vuex actions directly?
Try to call this._vm.$bvModal.show('modalId');.
Reference.
I think a better approach is to prevent to deal with the UI from the Store. So, you can add a store property and watch for changes from your components.
In the following example, I added an array toastMessages in the state property and a ADD_TOAST_MESSAGE mutation to add some toastMessage. You can then commit an ADD_TOAST_MESSAGE mutation from another mutation or from an action.
Inside your Top-level component (App.vue), you can watch for your toastMessages state property changes and display the last item that was pushed.
App.vue
<script>
export default {
name: "App",
created() {
this.$store.watch(
state => state.toastMessages,
toastMessages => {
this.$bvToast.toast(this.toastMessages.slice(-1)[0]);
}
);
}
}
</script>
store.js
export default new Vuex.Store({
state: {
toastMessages: []
},
mutations: {
ADD_TOAST_MESSAGE: (state, toastMessage) => (state.toastMessages = [...state.toastMessages, toastMessage]),
},
actions: {
myActionThatDoSomething({commit}, params) {
// Do something
commit('ADD_TOAST_MESSAGE', "Something happened");
}
}
});
found a solution here: https://github.com/vuejs/vuex/issues/1399#issuecomment-491553564
import App from './App.vue';
const myStore = new Vuex.Store({
state: {
...
},
actions: {
myAction(ctx, data) {
// here you can use this.$app to access to your vue application
this.$app.$root.$bvToast.toast("toast context", {
title: "toast!"
});
}
}
});
const app = new Vue({
el: '#my-div',
render: h => h(App),
store: myStore
});
myStore.$app = app; // <--- !!! this line adds $app to your store object
Using #eroak idea, I have implemented this same thing for vue-sweetalert2.
I have also created a store for my Sweet Alert Toaster, then I am watching a property called ticks that is updated whenever state of the toaster is updated.
I am using ticks as I only have one message in my state, and ticks just timestamp when action was triggered.
You can find whole demo here: https://github.com/Uraharadono/CallbackFromVuexAction
Try to call this._vm.$root.$bvModal.show('modalId');