I have this Vuex 2 store:
const datastore = new Vuex.Store({
state: {
socketcluster: {
connection: false,
channels: []
},
selected_offers: []
},
mutations: {
addOffer: function(offer) {
datastore.state.selected_offers.push(offer) // first problem: cannot just use state.offers as it throws an undefined
}
},
getters: {
getOffers: function(){
return datastore.state.selected_offers;
}
}
});
And inside a Vue 2 component I do:
methods: {
clicked: function(){
console.log("Toggle Offer");
if ( datastore.getters.getOffers.filter(function(o){ o.id == this.offer.id } ).length == 0 ) {
// add it
datastore.commit('addOffer', this.offer)
} else {
// TODO remove it
}
}
}
What happens is that when I trigger the method, the store changes as follows:
What am I doing wrong?
This is a simple way to work with vuex pattern, In big applications you should use actions instead of mutating the state directly from the component "like I did ", if so i urge you to read more about vuex.
const store = new Vuex.Store({
state: {
socketcluster: {
connection: false,
channels: []
},
selected_offers: [ "offer1", "offer2"]
},
mutations: {
addOffer: function( state, offer ) {
state.selected_offers.push(offer);
}
},
getters: {
getOffers: function( state ){
return state.selected_offers;
}
}
});
new Vue({
store,
data: function() {
return {
offer: "offer3"
}
},
methods: {
clicked: function() {
if ( this.offers.length === 2 ) {
this.$store.commit('addOffer', this.offer)
} else {
// TODO remove it
}
}
},
computed: {
offers: function() {
return this.$store.getters.getOffers;
}
}
}).$mount( "#app" );
<script src="https://vuejs.org/js/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/2.3.1/vuex.js"></script>
<div id="app">
<div v-for="offer in offers" > {{offer}}</div>
<button #click="clicked"> clicked </button>
</div>
The first parameter passed to a mutation is the state object. So, you're pushing the entire state object to the selected_offers array.
Your mutation method should look like this:
mutations: {
addOffer: function(state, offer) {
state.selected_offers.push(offer)
}
},
Here's the documentation for vuex mutations.
Related
I am new to VueJs and Laravel. I am trying to build a chat app following this tutorial, and my problem is that I am not able to pass a CONTACT variable (object type) from ChatApp.vue to Conversation.vue. The console gives this error
[Vue warn]: Invalid prop: type check failed for prop "contact". Expected Object, got Null
found in ---> <MessageFeed> at resources/js/officer/components/MessageFeed.vue
<Conversation> at resources/js/officer/components/Conversation.vue
<ChatApp> at resources/js/officer/components/ChatApp.vue
<Root>
first the variable has to pass through ChatApp to Conversation and then to MessageFeed. I tried printing it on console, in ChatApp.vue, it printed the variable's property i.e. name but when I try to print it in Conversation.vue, it says it is null and also gives the above error.
please see the code below:
ChatApp.vue
<template>
<div class="chat-app">
<Conversation :contact="selectedContact" :messages="messages" #new="saveNewMessage"/>
<ContactsList :contacts="contacts" #selected="startConversationWith"/>
</div>
</template>
<script>
import Conversation from "./Conversation";
import ContactsList from "./ContactsList";
const axios = require('axios');
export default {
props: {
user: {
type: Object,
required: true
}
},
data() {
return {
selectedContact: null,
messages: [],
contacts: []
}
},
mounted() {
Echo.private(`messages.${this.user.id}`)
.listen('NewMessage', (e) => {
this.handleIncoming(e.message);
});
console.log(this.user);
axios.get('/officer/contacts')
.then((response) => {
this.contacts = response.data;
console.log("chatapp1: " + this.contacts[0].name);
})
.catch((error) => {
// handle error
console.log(error);
});
},
methods: {
startConversationWith(contact) {
axios.get(`/officer/conversation/${contact.id}`)
.then((response) => {
this.message = response.data;
this.selectedContact = contact;
console.log("chatapp: " + this.selectedContact.name);
});
},
saveNewMessage(message) {
this.messages.push(message);
},
handleIncoming(message) {
if (this.selectedContact && message.from === this.selectedContact.id) {
this.saveNewMessage(message);
return;
}
this.updateUnreadCount(message.from_contact, false);
},
updateUnreadCount(contact, reset) {
this.contacts = this.contacts.map((single) => {
if (single.id !== contact.id) {
return single;
}
if (reset)
single.unread = 0;
else
single.unread += 1;
return single;
})
}
},
components: {Conversation, ContactsList}
}
Conversation.vue
<template>
<div class="conversation">
<h1>{{ contact ? contact.name : 'Select a Contact' }}</h1>
<MessagesFeed :contact="contact" :messages="messages"/>
<MessageComposer #send="sendMessage"/>
</div>
</template>
<script>
import MessagesFeed from './MessageFeed';
import MessageComposer from './MessageComposer';
export default {
props: {
contact: {
type: Object,
default: null
},
messages: {
type: Array,
default: []
}
},
mounted() {
console.log("conversation: "+this.contact);
},
methods: {
sendMessage(text) {
if (!this.contact) {
return;
}
console.log(text);
axios.post('/conversation/send', {
contact_id: this.contact.id,
text: text
}).then((response) => {
this.$emit('new', response.data);
})
}
},
components: {MessagesFeed, MessageComposer}
}
MessageFeed.vue
<template>
<div class="feed" ref="feed">
<ul v-if="contact">
<li v-for="message in messages" :class="`message${message.to == contact.id ? 'sent' : 'received'}`"
:key="message.id">
<div class="text">
{{message.text}}
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props: {
contact: {
type: Object,
required: true
},
messages: {
type: Array,
required: true
}
},
methods: {
scrollToBottom() {
setTimeout(() => {
this.$refs.feed.scrollTop = this.$refs.feed.scrollHeight - this.$refs.feed.clientHeight;
}, 1);
}
},
watch: {
contact(contact) {
this.scrollToBottom();
},
messages(messages) {
this.scrollToBottom();
}
}
}
please, let me know what am I missing here.
Because you konfigurated it to be an Object but you declared it on the initial state as null
selectedContact: null
That should be an object
It should look something like this i guess:
selectedContact: {
id:0
}
I fixed the issue.
This was due to axios.get() response thing. It was fetching data from database after a couple of minutes and the Conversation.vue and ContactList.vue were mounting immediately so they were not getting data.
With the help of my friend, it was fixed by loading the component only when axios have got all the contactlist from database and the very first contact have been passed to the Conversation.vue component i.e using v-if property of component.
I use a vuex store to handle a session and loading default sessions for different pages onCreated hook. Data is changing and most of the time it works fine but now I noticed even though I change the data, the setter / mutation is not called.
session.js (mixin to load default session from main component)
export default {
data () {
return {
defaultSession: {}
}
},
created () {
this.$store.dispatch('session/update', { data: this.defaultSession })
},
computed: {
sessionData: {
get () {
console.log('GETTER')
return this.$store.getters['session/sessionData']
},
set (session) {
console.log('SETTER')
this.$store.dispatch('session/update', { data: session })
}
}
}
}
store
export default {
namespaced: true,
state: {
session: {},
},
getters: {
session (state) {
return state.session
},
sessionData (state) {
return state.session.data
},
},
mutations: {
UPDATE (state, session) {
state.session = session
console.log('[Session] Updated')
}
},
actions: {
update: ({ commit }, data) => {
commit('UPDATE', data)
}
}
defaultSession example (in component)
defaultSession: {
name: '',
viewId: '',
defaultImage: null,
dataTable: [{
title: '',
data: [],
selected: [],
}],
imageTableData: [],
selectedIcons: []
},
So in my component I access <input v-model="sessionData.name"/> for instance, but neither the setter, nor the mutation is called. (The object is quite nested, because I also store table data inside.)
UPDATE (possible workaround):
watch: {
sessionData: {
deep: true,
handler() {
//commit changes here
}
}
}
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.
So I have the following code in one of my components:
export default {
name: 'section-details',
components: {
Loading
},
mounted() {
if (!this.lists.length || !this.section_types.length) {
this.$store.dispatch('section/fetch_section_form_data', () => {
if (this.section) {
this.populate_form();
}
});
}
else if (this.section) {
this.populate_form();
}
},
computed: {
section_types() {
return this.$store.state.section.section_types;
},
lists() {
return this.$store.state.list.lists;
},
loading() {
console.log(this.$store.state.section.loading);
this.$store.state.section.loading;
}
},
.
.
.
}
As you can see I have a computed property for "loading" that retrieves the attribute from my vuex store for when doing an ajax request.
in my section vuex module i have this:
fetch_section_form_data({ commit }, callback) {
commit("isLoading", true);
sectionService
.fetch_form_data()
.then((data) => {
commit("isLoading", false);
commit("fetch_section_types_success", data.section_types);
commit("list/fetch_lists_success", data.lists, { root: true});
if (callback) {
callback();
}
})
.catch((err) => {
commit("isLoading", false);
})
;
}
then in my mutations for the module i have the following code:
mutations: {
isLoading(state, status) {
state.loading = status;
},
}
Finally in my component where I store the loading property I have this:
<Loading v-if="loading"></Loading>
Anyways, for some reason the Loading component isn't showing up. the console.log in the loading() method however, is returning true for this.$store.state.section.loading. So for some reason Vue isn't picking up that loading == true in the actual DOM. Any help would be appreciated.
You need to return the value from the computed property method:
loading() {
return this.$store.state.section.loading;
}
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
}
}
}