Update state after using dispatch - javascript

Im using vuex and I have an action
storeExpense(context, params){
axios.post('api/expenses', params)
.then( response => {
console.log("Expense Created");
})
.catch( error => {
console.log(error);
});
}
and on my Expense.vue im using the action via
this.$store.dispatch('storeExpense',this.expense)
.then( response => {
this.modalShow = false
this.$swal(
'Success',
'Expense has been created!',
'success'
)
})
I dont have an error but after the expense was created the state is not updating therefore I need to refresh the page in order for my table to get the latest data.
I have a mutation called
mutateExpenses(state, payload){
state.expenses = payload
}
however when i use this after the response it overrides the whole state.expenses object to a single object because this.expense is a single object
Im new to vuex.

You must update your store using mutations that are called inside your actions.
I suggest you to dive a bit into the Vuex documentation, especially the mutations and actions :)
Here is an example of how to use the store :
It goes dispatch --> action --> mutation
// Your store
const store = new Vuex.Store({
state: {
posts: [],
isLoading: false
},
mutations: {
// Must be called by actions AND ONLY by actions
add(state, post) {
// Add the given post to the 'posts' array in our state
Vue.set(state.posts, state.posts.length, post)
},
busy(state) {
Vue.set(state, 'isLoading', true)
},
free(state) {
Vue.set(state, 'isLoading', false)
}
},
actions: {
create({
commit
}, post) {
commit('busy')
axios.post('https://jsonplaceholder.typicode.com/posts', post)
.then(response => {
// Call the mutation method 'add' to add the newly created post
commit('add', response.data)
})
.catch((reason) => {
// Handle errors
})
.finally(() => {
commit('free')
});
},
}
})
// Your Vue app
new Vue({
el: "#app",
store,
data: {
post: {
title: 'foo',
body: 'bar',
userId: 1
}
},
methods: {
onButtonClicked() {
this.$store.dispatch('create', this.post)
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<button #click="onButtonClicked">Create Post</button>
<div>Posts : <span v-if="$store.state.isLoading">Requesting</span></div>
<div v-for="post in $store.state.posts">
{{post}}
</div>
</div>

Related

Vuex dispatch doesn't return data in view

I cannot get my data from vuex and use them in all views, but i do see their success results in network tab.
screenshots
network tab
console
Code
store.js
state: {
currency: {}
},
mutations: {
currency(state, currency){
state.currency = currency
}
},
actions: {
currency({commit}){
return new Promise((resolve, reject) => {
commit('currency')
axios({url: '/api/currencyDefault', method: 'GET' })
.then(resp => {
const currency = resp.data.data
commit('currency', currency)
resolve(resp)
})
.catch(err => {
commit('currency')
reject(err)
})
})
},
},
getters: {
currency: state => state.currency,
}
App.vue (main component where routers will load)
<script>
export default {
props:['currency'],
data() {
return {
isCollapse: true,
}
},
created () {
this.currency()
},
methods: {
currency() {
this.$store.dispatch('currency')
}
},
}
</script>
An then in my other component i call for currency like:
{{currency.name}}
ideas?
Method Currency is already defined as props. remove this in your code.
props:['currency']
Ten call this currency in your component like this
<div>
{{this.$store.getters.currency.name}}
</div>
to surely display the currency, what I did is to put first a condition to check if it is was already loaded. like this
<div v-if="$store.getters.currency">
{{this.$store.getters.currency.name}}
</div>
or declare a new variable in your data like
data() {
return {
currency: this.$store.getters.currency.name
}
}
now you could call it this way
<div v-if="$store.getters.currency">
{{currency.name}}
</div>

vue.js component not updated after vuex action on another component

I've a component which render a booking table; When I update my store in another component, the table isn't updated (but the store does and so the computed properties; My guess is that the problem is related to the filter not being updated but I'm not sure at all.
To do so, I've a vuex store:
...
const store = new Vuex.Store({
state: {
bookings: [],
datesel: '',
},
getters: {
bookings: (state) => {
return state.bookings
},
},
mutations: {
SET_BOOKINGS: (state, bookings) => {
state.bookings = bookings
},
},
actions: {
setBookings: ({commit, state}, bookings) => {
commit('SET_BOOKINGS', bookings)
return state.bookings
},
}
})
export default store;
The table is basically a v-for with a filter:
<template v-for="booking in getBookings( heure, terrain )">
Where getBookings is a method:
getBookings(hour, court) {
return this.$store.state.bookings.filter(booking => booking.heure == hour && booking.terrain == court);
}
I've another component which will update my bookings state through a method:
bookCourt() {
axios.post('http://localhost/bdcbooking/public/api/reservations/ponctuelles',
{
date: this.datesel,
membre_id: '1',
heure: this.chosenHour,
terrain: this.chosenCourt,
saison_id: '1'
})
.then(response => {
// JSON responses are automatically parsed.
console.log(response.data);
})
.catch(e => {
this.errors.push(e)
})
axios.get('http://localhost/bdcbooking/public/api/getReservationsDate?datesel=' + this.datesel)
.then(response => {
// JSON responses are automatically parsed.
console.log(response.data);
this.bookings = response.data;
})
.catch(e => {
this.errors.push(e)
})
$(this.$refs.vuemodal).modal('hide');
}
While this.bookings is a computed property:
computed: {
bookings: {
get () {
return this.$store.getters.bookings
},
set (bookings) {
return this.$store.dispatch('setBookings', bookings)
console.log('on lance l action');
}
}
}
Your table is not updated because getBookings is a simple method and hence the method won't be fired again based on vuex state changes.
You can make this getBookings method as an computed property that returns filtered results and will also upadte on state changes.

How to pass data from Vuejs to vuex Store?

I have a vuejs component and a vuex store.
I would like to send data from vue component to vuejs store and then call a function in vuex that's push data to a db.
I get the data from currentUser (that works), but in vuex store I get the error: Cannot read property 'push' of null.
I run createPost that works but the data does not pushed to vuex store I think because the error above.
#vuejs component
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";
import {
SET_NEWPOST,
ADD_TAGS,
SET_USERDATA,
SET_GENERAL
} from "#/store/posts/mutations";
methods: {
...mapMutations("posts", {
updateInformation: SET_NEWPOST,
setUserData: SET_USERDATA,
addGeneral: SET_GENERAL,
addTags: ADD_TAGS
}),
...mapActions("posts", {
create: "triggerAddProductAction"
}),
async createPost() {
this.updateInformation({
content: this.content,
url: this.newOne
});
this.updateUserData();
this.createOne();
}
}
vuex store
...
const state = {
products: []
}
const mutations = {
[addProduct]: (state, product) => state.products.push(product)
},
const actions: {
createUserProduct: async ({ commit, rootState }, product) => {
const userProductDb = new UserProductsDB(
rootState.authentication.user.id
);
const createdProduct = await userProductDb.create(product);
commit("addProduct", createdProduct);
},
triggerAddProductAction: ({ dispatch, state, commit }) => {
const post = state.newPost;
dispatch("createUserProduct", post);
}
}
Your format I believe is a little off. Try building the store like this. Remember that using arrow functions vs non-arrow functions can also have a side effect in what is being referenced.
Mostly what can be seen, is that I removed the const's, and placed it all in the object literal directly. I also remove the Destructuring of addProduct as it doesn't seem logical here.
const store = new Vuex.Store({
state: {
products: []
},
mutations: {
addProduct: (state, product) => {
state.products.push(product)
console.log('Added Product:', product)
console.log('products', state.products)
}
},
actions: {
async createUserProduct({ commit }, product) {
commit("addProduct", product);
}
}
});
new Vue({
el: "#app",
store,
mounted() {
this.$store.dispatch('createUserProduct', 1)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.0/vuex.min.js"></script>
<div id="app"></div>
I think one of the main problems here is actually that you call mutations directly in your component. Mutations should always be called by actions and not directly. This is because mutations are synchronous and actions can be asynchronous. From Vuex docs:
On to Actions
Asynchronicity combined with state mutation can make your program very hard to reason about. For example, when you call two methods both with async callbacks that mutate the state, how do you know when they are called and which callback was called first? This is exactly why we want to separate the two concepts. In Vuex, mutations are synchronous transactions:
store.commit('increment')
// any state change that the "increment" mutation may cause
// should be done at this moment.
To handle asynchronous operations, let's introduce Actions.
That's why you should have a structure like this:
export const mutations = {
ADD_EVENT(state, event) {
state.events.push(event)
},
SET_EVENTS(state, events) {
state.events = events
},
SET_EVENTS_TOTAL(state, eventsTotal) {
state.eventsTotal = eventsTotal
},
SET_EVENT(state, event) {
state.event = event
}
}
export const actions = {
createEvent({ commit, dispatch }, event) {
return EventService.postEvent(event)
.then(() => {
commit('ADD_EVENT', event)
commit('SET_EVENT', event)
const notification = {
type: 'success',
message: 'Your event has been created!'
}
dispatch('notification/add', notification, { root: true })
})
.catch(error => {
const notification = {
type: 'error',
message: 'There was a problem creating your event: ' + error.message
}
dispatch('notification/add', notification, { root: true })
throw error
})
}
Check also this video out by vuemastery even featured on the official vuex docs: https://www.vuemastery.com/courses/mastering-vuex/intro-to-vuex/

vue mapGetters not getting on time

I'm using vuex to manage the state in my application and doing one way binding with my form.
<script>
import { mapGetters } from 'vuex'
import store from 'vuex-store'
import DataWidget from '../../../../uiComponents/widget'
export default {
data () {
return {
isEdit: false,
msg: {
id: 0,
content: '',
isEnabled: false
}
}
},
components: {
DataWidget
},
computed: mapGetters({
messageId: 'messageId',
messageContent: 'messageContent',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
methods: {
onSave () {
store.dispatch('saveMessage', this.msg, { root: true })
if (this.isMessageValid) {
this.isEdit = !this.isEdit
}
}
},
created () {
this.msg.id = this.messageId
this.msg.content = this.messageContent
this.msg.isEnabled = this.isMessageEnabled
}
}
</script>
<b-form-textarea id="content" v-model="msg.content" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
On load, the values on created() are not binded until I perform an action on the page or refresh the page.
I have tried mounted () hooked same thing.
My Vuex store (Message Module) looks like this:
const state = {
messageId: 0,
messageContent: '',
isMessageEnabled: false,
isMessageValid: true
}
const getters = {
messageId: state => state.messageId,
messageContent: state => state.messageContent,
isMessageEnabled: state => state.isMessageEnabled,
isMessageValid: state => state.isMessageValid
}
const actions = {
getMessage ({commit, rootGetters}) {
api.fetch('api/Preference/Message', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data)
})
}
}
const mutations = {
[types.MESSAGE_LOAD] (state, payload) {
state.messageId = payload ? payload.id : 0
state.messageContent = payload ? payload.content : ''
state.isMessageEnabled = payload ? payload.enabled : false
}
}
export default {
state,
getters,
actions,
mutations
}
and I have a global action (action.js) the gets multiple data:
export const loadSetting = ({ commit, rootGetters }) => {
api.fetchAsync('api/Preference/all', rootGetters.token)
.then((data) => {
commit(types.MESSAGE_LOAD, data.message)
commit(types.HELPDESK_LOAD, data.helpDesk)
commit(types.VOLUME_LOAD, data.volumes)
commit(types.DOWNLOAD_LOAD, data.downloadService)
})
}
My api call:
async fetchAsync (url, token = '') {
let data = await axios.get(HOST + url, {
headers: {
'Authorization': 'bearer ' + token
}
})
return data
}
The problem is your'e calling an async method in Vuex but in the created method, you're treating it like a sync operation and expect to get a value.
You need to use the computed properties you created since they are reactive and will update on every change. In order to make the computed writeable change it to be like this:
computed: {
...mapGetters({
messageId: 'messageId',
isMessageEnabled: 'isMessageEnabled',
isMessageValid: 'isMessageValid'
}),
messageContent(){
get () {
return this.$store.getters.messageContent
},
set (value) {
//this is just an example, you can do other things here
this.$store.commit('updateMessage', value)
}
}
}
And change the html to use messageContent:
<b-form-textarea id="content" v-model="messageContent" :rows="3" required aria-required="true" maxlength="250"></b-form-textarea>
For more info refer to this: https://vuex.vuejs.org/en/forms.html

Get Vue to update view/component

I'm stuck at a crossroads with a component I am working on.
I have the following component "RecentUpdates"
Within it I am passing props down to a few other components, as you can see from the top of the file.
My problem is when adding a new post, I can not figure out how to get the correct update object array back and i also can not figure out the correct 'Vue way' to update the data prop that is being passed down to the "PostList" component.
<template>
<div>
<PostFilter v-on:selectedCategory="getSelectedPosts" v-on:showAllPosts="showAllPosts" :user="user" :categories="categories"/>
<PostList v-if="recent_posts[0]" :categories="categories" :posts="recent_posts[0]" :user="user"/>
<Pagination v-on:getPreviousPage="getPreviousPage" v-on:getNextPage="getNextPage"/>
</div>
</template>
<script>
import PostList from './PostList';
import PostFilter from './PostFilter';
import Pagination from './Pagination';
import EventBus from '../event-bus';
export default {
name: 'RecentUpdates',
data: () => ({
errors: [],
recent_posts: [],
}),
props: ['categories', 'user'],
components: {
PostList,
PostFilter,
Pagination
},
created() {
if (this.user.meta.selected_categories[0] == 0) {
this.showAllPosts();
}
// do not call here, not working as expected
// is switching selected category to an incorrect one
// this.updateList();
this.getSelectedCategory();
},
watch: {
recent_posts: function(newValue) {
EventBus.$on('addPost', function(newPost) {
console.log(newPost);
this.$forceUpdate();
//this.recent_posts.push(newPost);
//this.$set(this.recent_posts, newPost, newPost);
// this.$nextTick(function () {
// this.recent_posts.push(newPost);
// });
});
console.log(this.recent_posts[0]);
// this.$nextTick(function () {
// console.log(this.recent_posts[0]) // => 'updated'
// });
// if (this.user.meta.selected_categories[0] == 0) {
// EventBus.$on('addPost', this.showAllPosts);
// } else {
// EventBus.$on('addPost', this.getSelectedCategory);
// }
//this.updateList();
}
},
methods: {
// updateList() {
// if (this.user.meta.selected_categories[0] == 0) {
// EventBus.$on('addPost', this.showAllPosts);
// //EventBus.$emit('newPost');
// } else {
// EventBus.$on('addPost', this.getSelectedCategory);
// //EventBus.$emit('newPost');
// }
// },
getSelectedCategory() {
let categoryId = this.user.meta.selected_categories[0];
this.getSelectedPosts(categoryId);
},
showAllPosts() {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]',
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = [];
//this.recent_posts = response.data;
//console.log(response.data);
this.recent_posts.push(response.data);
console.log(this.recent_posts[0]);
})
.catch(e => {
this.errors.push(e);
});
},
getSelectedPosts(categoryId) {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&categories=' + categoryId,
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = [];
//console.log(response.data);
this.recent_posts.push(response.data);
console.log(this.recent_posts[0]);
})
.catch(e => {
this.errors.push(e);
});
},
/**
* Pagination methods
*
*/
getPreviousPage(page) {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&page=' + page,
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = response.data;
})
.catch(e => {
this.errors.push(e);
});
},
getNextPage(page) {
axios.get('/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&page=' + page,
{headers: {'X-WP-Nonce': portal.nonce}})
.then(response => {
this.recent_posts = response.data;
})
.catch(e => {
this.errors.push(e);
});
}
},
}
</script>
<style>
</style>
So there are a number of issues I see reading through your code.
You have a recent_posts data property, which is an array. When you make your ajax call to get the posts you push the response which is also an array into the recent_posts array. Why? Why not just set recent_posts = response.data? Then you won't have to be passing recent_posts[0] around.
You're setting up your EventBus handler inside a watcher. This is really unusual. Typically you would set up a handler inside created or mounted.
this inside the EventBus handler likely refers to the EventBus and not your Vue. Ideally, you would set the handler to be a method on the component, which is already bound to the Vue. Something like EventBus.$on("addPost", this.addPost).
Once you've done all that, adding a new post should be as simple as this.recent_posts.push(newPost).
Here is what I might recommend.
export default {
name: 'RecentUpdates',
data(){
return {
errors: [],
recent_posts: []
}
},
props: ['categories', 'user'],
components: {
PostList,
PostFilter,
Pagination
},
created() {
if (this.user.meta.selected_categories[0] == 0) {
this.showAllPosts();
}
this.getSelectedCategory();
EventBus.$on("addPost", this.addPost)
},
beforeDestroy(){
EventBus.$off("addPost", this.addPost)
},
methods: {
getPosts(url){
axios.get(url, {headers: {'X-WP-Nonce': portal.nonce}})
.then(response => this.recent_posts = response.data)
.catch(e => this.errors.push(e))
},
showAllPosts() {
const url = '/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]';
this.getPosts(url);
},
getSelectedPosts(categoryId) {
const url = '/wp-json/wp/v2/posts?_embed=true&status=[publish,resolved,unresolved]&categories=' + categoryId;
this.getPosts(url);
},
addPost(newPost){
this.recent_posts.push(newPost)
},
... //other methods
},
}
Try using kebab-case in your event listeners instead of camelCase:
Example: v-on:selectedCategory="getSelectedPosts" should be v-on:selected-category="getSelectedPosts".
Example: v-on:showAllPosts="showAllPosts" should be v-on:show-all-posts="showAllPosts" or even using the shortcut #show-all-posts="showAllPosts".
UPDATE: If you can provide the code of the other components so we can have a clearer vision of your problem, But you only want to track changes that happens on an object or an array in vue.js you need to deep watch them.
your watcher should be :
watch: {
recent_posts: {
deep: true,
handler: function( oldValue, newValue) {
console.log( "recent_posts has changed" );
// A post has been added, updated or even deleted
}
}
}

Categories