Building a VueJs component with xState and state machines

We are currently used to managing state by using properties; usually, booleans, to toggle parts of the UI.

The problem with this is, the more complex your UI grows, the more booleans and conditionals you’ll have, turning everything into a big pile of meaningless conditionals that are hard to reason about. Once enough time passes, you will find it increasingly difficult to understand what’s going on because there’s no easy way of figuring out your component’s flow without clicking around and checking the code simultaneously.

State machines help us reduce the number of conditionals we have in our code, make having conflicting states virtually impossible, and even offer a way to visualize the flow of our components so we can easily track how and when we end up in a specific state.

Looking at this component below, we can see conditionals and booleans scattered all over the place. We can make some guesses based on our previous experiences, but we’re not really sure when and where each of those booleans change, especially when they come in combinations.

// ContactForm.vue
<template>
  <div class="container">
    <button
      v-if="!showForm && !showConfirmCancellation && !showSuccessMessage"
      @click="openForm()"
      class="btn-primary"
    >
      Send us a message
    </button>

    <form v-if="showForm" @submit.prevent="submit">
      <div class="mb-4">
        <label for="full-name">Full name</label>
        <input
          v-model="form.name"
          class="field"
          id="full-name"
          type="text"
          placeholder="Your full name"
        />
      </div>
      <div class="mb-4">
        <label for="email">E-mail</label>
        <input
          v-model="form.email"
          class="field"
          id="email"
          type="text"
          placeholder="Your e-mail address"
        />
      </div>
      <div class="mb-4">
        <label for="message">Message</label>
        <textarea
          v-model="form.message"
          name="message"
          id="message"
          rows="5"
          placeholder="Type in your message"
          class="field"
        >
        </textarea>
      </div>
      <div class="flex justify-end">
        <button @click="cancelSubmission()" class="btn-secondary" type="button">
          Cancel
        </button>
        <button class="btn-primary" :disabled="isSubmitting">
          {{ isSubmitting ? "Submitting" : "Submit" }}
        </button>
      </div>
    </form>

    <div v-if="showConfirmCancellation">
      <div class="mb-4">
        Are you sure you want to cancel? Your form will be cleared.
      </div>

      <div class="flex justify-end">
        <button @click="cancelCancellation()" class="btn-secondary">
          No, go back.
        </button>
        <button @click="confirmCancellation()" class="btn-primary">
          Yes, cancel.
        </button>
      </div>
    </div>

    <div v-if="showSuccessMessage" class="text-green">
      <div class="mb-4">Hey! Thanks for your message!</div>
      <div class="flex justify-end">
        <button @click="sendAnotherMessage()" class="btn-primary">
          Send another message
        </button>
      </div>
    </div>
  </div>
</template>

Having so many conditionals in so many different places increases the chances of introducing unwanted side effects. One misplaced boolean, and everything stops working properly.

Picked your interest? Watch the video below I take that VueJS contact form component and rebuild it using xState and state machines.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.