6

I'm trying to build a multi-step form wizard following the official Vee-Validate guide, the example it provides is found here. While this is helpful, I want to incorporate Vuex into this, and I'm using custom components instead of Vee-Validate's default <Form/> and <ErrorMessage/> components. The custom components do not use the Vee-Validate default components internally.

Doing this has brought me some problems however. My current problem is that I can't find out how to get my Vuex state into Vee-Validate reactively.

My text input in my main component is used like this:

    <text-input
        label="Name"
        name="name"
        v-model="name"
    />
    ...
computed: {
  ...mapGetters({
      businessFields: 'business/businessFields',
  }),
  name: {
      get () {
          return this.businessFields.name;
      },
      set (value) {
          this.$store.commit('business/setName', value)
      }
  },
},

This is all according to the two-way computed property guide that Vuex gives here.

Now I'm trying to incorporate these fields into the Vee-Validate multi form wizard linked earlier. I've basically changed it to get the values while resetting and going back and forth from Vuex instead of the local values. Like so:

<!-- FormWizard.vue (from the Vee-Validate example adapted to use Vuex) -->
<template>
  <form @submit="onSubmit">
    <slot />

    <div>
      <button v-if="hasPrevious" type="button" @click="goToPrev">
        Previous
      </button>
      <button type="submit">{{ isLastStep ? "Submit" : "Next" }}</button>
    </div>
  </form>
</template>

<script>
import { useForm } from "vee-validate";
import { useStore } from 'vuex';
import { ref, computed, provide } from "vue";

export default {
  name: "FormWizard",
  emits: ["next", "submit"],
  props: {
    validationSchema: {
      type: null,
      required: true,
    },
    formDataGetter: {
      type: String,
      required: true,
    }
  },
  setup(props, { emit }) {
    const store = useStore();
    const currentStepIdx = ref(0);

    // Injects the starting step, child <form-steps> will use this to generate their ids
    const stepCounter = ref(0);
    provide("STEP_COUNTER", stepCounter);
    // Inject the live ref of the current index to child components
    // will be used to toggle each form-step visibility
    provide("CURRENT_STEP_INDEX", currentStepIdx);

    // if this is the last step
    const isLastStep = computed(() => {
      return currentStepIdx.value === stepCounter.value - 1;
    });

    // If the `previous` button should appear
    const hasPrevious = computed(() => {
      return currentStepIdx.value > 0;
    });

    const { resetForm, handleSubmit } = useForm({
      validationSchema: props.validationSchema,
    });

    // We are using the "submit" handler to progress to next steps
    // and to submit the form if its the last step
    // parent can opt to listen to either events if interested
    const onSubmit = handleSubmit(() => {
      // Sets initial values for the values already filled
      // effectively fills the inputs when clicking on "previous"
      resetForm({
        values: {
          ...store.getters[props.formDataGetter]
        },
      });
      if (!isLastStep.value) {
        currentStepIdx.value++;
        emit("next", store.getters[props.formDataGetter]);

        return;
      }

      emit("submit");
    });

    function goToPrev() {
      if (currentStepIdx.value === 0) {
        return;
      }

      currentStepIdx.value--;
    }

    return {
      onSubmit,
      hasPrevious,
      isLastStep,
      goToPrev,
    };
  },
};
</script>

The FormStep.vue step is exactly the same. When I'm using the FormWizard like so:

  <form-wizard :validationSchema="validationSchema" @submit="onSubmit" formDataGetter="setup/setupFields">
    <form-step>
      <business-form></business-form>
    </form-step>
  </form-wizard>

With a setup() in my main component which houses the FormWizard like so:

  setup() {
    const store = useStore();
    // You don't have to validate each step
    // vee-validate will only validate the fields rendered from the schema
    // fields not rendered errors will be ignored, so it all just works!
    const validationSchema = yup.object().shape({
      name: yup.string().required(),
    });

    /**
     * Only Called when the last step is submitted
     */
    const onSubmit = (values) => {
      console.log(values);
      alert(store.getters['business/businessFields'].name);
    };

    return {
      validationSchema,
      onSubmit,
    };
  },

The thing is now that I'm going to the next page, I receive a validation error that name is required. This is due to not using Vee-Validate's useField() function to house my fields. useField() returns a computed property that you have to link as a v-model to your input. The problem that comes here is that I can't change the Vuex field when I use useField(), and I can't change the useField with Vuex' computed property since that isn't an option in Vee-Validate.

Now my question is: how do you implement Vee-validate 3 and Vuex 4 in a way that allows both updating my Vuex store and my Vee-Validate useForm() fields with only one computed property?

Or is there a different approach to this?

zcoop98
  • 2,590
  • 1
  • 18
  • 31
Danoctum
  • 321
  • 5
  • 16

0 Answers0