CroudTech / vue-fullcalendar

FullCalendar Wrapper for vue
MIT License
483 stars 100 forks source link

Error: [vuex] Do not mutate vuex store state outside mutation handlers. #33

Closed smingam closed 7 years ago

smingam commented 7 years ago

Hello, I'm quite new to the wonderfull world of Vue and trying to use this very useful wrapper with a vuex store... I'm kind of tearing my hairs appart with this problem! Here's what i'm doing, nothing fancy I think:

<full-calendar ref="calendar" :events="eventsFromStore" @event-resize="eventResize" @event-created="eventCreated" @event-drop="eventMoved"></full-calendar>

...
    computed: {
        ...mapGetters({    
            eventsFromStore: 'allEvents'
        })
    }
...
    methods: {
        'eventResize' (event) {
            this.$store.dispatch('updateEvent', event)
        },
        'eventCreated' (event) {
            this.$store.dispatch('createEvent', event)
        },
        'eventMoved' (event) {
            this.$store.dispatch('updateEvent', event)
        }
    },
    created () {
        this.$store.dispatch('getAllEvents')
    }

It appears that the events array is passed to fullcalendar which is directly modifying it, which is not allowed in strict mode. I tried to mitigate that by having a cloned array passed to events, to no avail (the vue store functions are cloned too?). There is also a problem of infinite loop in the events watcher, so I'm wondering if it's me or if there is something else. From what I understand, removing events modify this.events so vue trigger the watcher again.

watch: {
            events: {
                deep: true,
                handler(val) {
                    $(this.$el).fullCalendar('removeEvents')    // this is triggering this same watcher on events!
                    $(this.$el).fullCalendar('addEventSource', this.events)
                },
            }
}

From what I've gather, cloning the array coming from the props is a good practice, but since I'm new to vue, I'm wondering what are the side effects... Here is what i'm doing, any thoughts? :

    created () {
        this.clonedEvents = _.cloneDeep(this.events)
    },

    data () {
        return {
            clonedEvents: []
        }
    },

    watch: {
        events: {
            deep: true,
            handler (val, old) {
// if( _.difference(val, old).length > 0)   // wouldn't hurt (but perf?) to check if there is really a difference before updating
                this.clonedEvents = _.cloneDeep(this.events)
                $(this.$el).fullCalendar('removeEvents')
                $(this.$el).fullCalendar('addEventSource', this.clonedEvents)
            },
        }
    }

Thanks for your help!

smingam commented 7 years ago

Ok, I'm confused, I've got something working. More code:

//  store.js
const state = {
    events: []
}

const getters = {
    allEvents: state => state.events
}

const actions = {
    getAllEvents ({ commit }) {
        // call (async) api end point
        eventsApi.getEvents()
            .then((events) => {
                // then commit mutation on receive of objects
                commit(types.RECEIVE_EVENTS, events)
            })
            .catch((err) => console.log(err))
    }
}
const mutations = {
    [types.RECEIVE_EVENTS] (state, events) {
        [...state.events] = events
    }
}

// eventsApi.js
/**
 * Mocking client-server processing
 */
const _events = [
    { ... }, { ... }
]
...
    getEvents: function () {
        return new Promise((resolve, reject) => {
            resolve(_.cloneDeep(_events))
            // resolve(_events) <-- that doesn't work but with the above it's ok.
        })
    }

Is it just because I was returning a const array to fullcalendar? I've got more to learn... Sorry guys!

BrockReece commented 7 years ago

Hi @smingam, welcome to the wonderful world of Vue 👍

I agree that I could / should be cloning the prop before passing it into fullcalendar, to be honest though, I have never experienced a problem with the current set up.

If you are loading events via an API request, have you considered this approach?

Maybe I am over simplifying your setup though...?

Cheers Brock

smingam commented 7 years ago

Hi @BrockReece , thanks for your answer!

I haven't tried the eventSources, but yes, as I was reading more code and answers here, I was thinking of it. I'm very early into this project, so, nothing fixed. What I'm trying to do is a calendar with a REST backend, with every action going to the server for validation. So as I was reading about Vuex, I liked the separation of concerns and testability (the backend is not done yet, hence my poor mocking), but perhaps it's too much? Perhaps a json feed and a event-refresh after each actions would be enough... I'll try to see if I can set up an jsfiddle or something for the watcher loop, event if it was probably mistake on my side, it could help others.

BrockReece commented 7 years ago

No problem.

When you are a bit further on and if you are happy to share your code, give me a shout and I can see if we can get it working.

Cheers Brock