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?