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

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.

Related

Using array.sort() on Vue data() variable

I'm loading Api data into a reactive variable called 'MyListings', and attempting to sort it based on latest id -> earliest id.
The problem I'm having is that since array.sort is an 'in-place' function it overwrites the original array which causes the Vue page to infinitely re-render.
Is there some way to clone the data out of data()? (I tried setting a constant to this.MyListings which produced the same results as the original)
api data
[
{
id: 1,
title: "sdf",
start_date: "2021-01-13",
poster:
"http://localhost:8000/media/images/2-Artwork-by-Tishk-Barzanji_iZookmb.jpg",
genre: "Action"
},
{
id: 3,
title: "ooga booga",
start_date: "2021-01-07",
poster: "http://localhost:8000/media/images/1_bdm0Lld.jpg",
genre: "Thriller"
}
];
Relevant Vue bits
updated() {
this.sortedArray();
},
data() {
return {
genre: "",
MyListings: []
};
},
methods: {
// no access to AsnycData in components, handle the incoming data like axios in vanilla vue.
memberListings($axios) {
this.$axios
.get("/api/v1/memberlistings/")
.then(response => {
// console.log(response, "response");
this.MyListings = response.data;
})
.catch(error => {
console.log(error);
});
},
formSubmit() {
this.update(); //all dropdown combinations below include a POST request
},
update() {
if (this.genre !== "") {
console.log("genre submitted");
this.$axios
.get(`/api/v1/memberlistings/`, {
params: {
genre: this.genre
}
})
.then(response => {
this.MyListings = response.data;
})
.catch(error => {
console.log(error);
});
} else {
this.$axios
.get(`/api/v1/memberlistings/`)
.then(response => {
this.MyListings = response.data;
})
.catch(error => {
console.log(error);
});
}
},
sortedArray() {
const sortedMyListings = this.MyListings;
sortedMyListings.sort((a, b) => b.id - a.id);
console.log(sortedMyListings, "sortedlist");
console.log("works");
}
}
**This is what the endless rendering looks like in console log **
[1]: https://i.stack.imgur.com/CTVAH.png
I believe, you want to display data in sorted order. hence, you should create a computed property.
computed:{
sortedData() {
return this.MyListings.map(item=>item).sort((a,b)=> a.id - b.id)
}
},
and use that computed property in template
<template>
<div id="app">
<div v-for="item in sortedData" :key="item.id">
{{item.id}}-{{item.title}}
</div>
</div>
</template>

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>

Error in v-on handler: "TypeError: Cannot read property 'emailAddress' of undefined" VUEX

I'm having trouble figuring this out with the vuex store. I have a created a subscription form to be able to enter your email address and I want to make a post request to the backend api but the data is not showing when I console.log it. It only console.logs the item that has been dispatch to the store and when I'm trying to mutate the item to the state.emailAddress and try to get the data that to the POST I get an empty object. enter image description here
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
pizzaItems: [],
shopCart: [],
choosePizzaSize: [],
total: 0,
deliveryCharge: 0,
emailAddress: '',
},
getters: {
deliveryCharge: state => state.deliveryCharge,
pizzaItems: state => state.pizzaItems,
shopCart: state => state.shopCart,
choosePizzaSize: state => state.choosePizzaSize,
checkOut: state => state.total,
},
mutations: {
Add_To_Cart(state, item) {
const shopItem = state.shopCart.find(x => x.id === item.id && x.size === item.size);
if (shopItem) {
shopItem.count += item.count;
} else {
state.shopCart.push(item);
}
},
Price_Total(state, total) {
state.total = total;
},
fetchProducts(state, products) {
state.pizzaItems = products;
},
fetchPizzasize(state, size) {
state.choosePizzaSize = size;
},
free_Shipping(state, freeshipping) {
state.deliveryCharge = freeshipping;
},
subScribeMail(state, item) {
state.emailAddress = item;
},
},
actions: {
fetchData({ commit }) {
axios.get('http://localhost:3000/saltbageproducts').then((response) => {
commit('fetchProducts', response.data.products);
commit('fetchPizzasize', response.data.pizzasize[0].pizzaSize);
})
.catch((err) => {
console.log(err);
});
},
addTocart(context, item) {
context.commit('Add_To_Cart', item);
},
priceCalculation(context, item) {
context.commit('Price_Total', item);
},
changeFreeshippingStatus(context, shipping) {
context.commit('free_Shipping', shipping);
},
emailCheckUp(context, item, state) {
context.commit('subScribeMail', item);
axios.post('http://localhost:3000/saltbagehome', {
email: state.emailAddress,
}).then((reponse) => {
console.log(reponse.data);
}).catch((err) => {
console.log(err);
});
},
},
modules: {
},
});
Acoording Vuex documentation;
Register actions on the store. The handler function receives a context
object that exposes the following properties:
{
state, // same as `store.state`, or local state if in modules
rootState, // same as `store.state`, only in modules
commit, // same as `store.commit`
dispatch, // same as `store.dispatch`
getters, // same as `store.getters`, or local getters if in modules
rootGetters // same as `store.getters`, only in modules
}
And also receives a second payload argument if there is one.
Then in your first parameter of your action u have the vuex context, there should be:
emailCheckUp(context, item) { ... }
instead of
emailCheckUp(context, item, state) { ... }
now to access to vuex context inside of your action, just:
emailCheckUp(context, item) {
context.commit('subScribeMail', item);
...
email: context.state.emailAddress,
...
}
In practice, they often use ES2015 argument destructuring to simplify the code a bit:
emailCheckUp({commit, state}, item) {
commit('subScribeMail', item);
...
email: state.emailAddress,
...
}

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