Search code examples
vue.jsvuetify.jsvuexstate-machinexstate

XState doesn't stay in idle state


I am using xstate together with vuex in my application:

  • vuex handles the user, api calls, response items, etc.
  • xstate handles mainly complex form interactions.

Due to the reason, that complex forms contain multiple components, which communicate with each other I have to implement xstate not on a component level, but inside vuex. Inside the xstate visualizer it seems to work:

enter image description here

enter image description here

I can switch between the state, but in the real application it can't go back to its initial state (idle) and then stay there. It always transitions back to the next one. I don't know what I am doing wrong, but I have console logged the formState getter and the output looks like this:

formState idle 
formState createImageContent 

(it immediately switches away from idle state)

Here is the basic example of the implementation: https://codesandbox.io/s/boring-shape-b4lgk


Solution

  • Problem is in your Form.vue component. In this line to be precise:

    <v-window v-model="$store.getters['form/formState'].value">
    

    I'm no Vuetify expert so I do not know why exactly is v-window updating the v-model with the value createImageContent but it definitely does. I found the problem by activating Vuex strict mode which raises error whenever some state is mutated outside of mutation. It throws an error and from the stack trace you can see the problem is caused by v-window mutating the $store.getters['form/formState'].value

    Easy fix is not using v-model and use :value instead:

    <v-window :value="$store.getters['form/formState'].value">
    

    Demo

    Update

    OK, I found the reason. v-window has a undocumented prop mandatory (source) which is true by default. In this mode, the v-window is expecting (and enforcing) that at least one of <v-window-item> child components is active at all times. When you set state to idle, there is no <v-window-item> with such value so it picks one of the items itself (first not disabled) and updates v-model. So another solution is to add :mandatory="false" to <v-window>.

    But not using v-model is probably safer and cleaner as Vuex getter should never be used with v-model