There is component that dynamically renders a form, depending on the fields that are passed on it by props. The issue is that validations are not updated, I've been trying for hours to make it work when the props changes (they change based on a boolean in its parent component), but I couldn't make it.
This is the code:
<script setup>
import { reactive, computed, watch, set } from "vue";
import { useVuelidate } from "@vuelidate/core";
const props = defineProps({
fields: {
type: Array,
required: true,
},
reward: {
type: Object,
required: true,
},
});
const emit = defineEmits(["submit"]);
let formData = reactive(
props.fields.reduce((acc, field) => {
acc[field.name] = null;
return acc;
}, {})
);
let validations = reactive(
props.fields.reduce((acc, field) => {
acc[field.name] = { ...field.validations };
return acc;
}, {})
);
let state = reactive({
v$: useVuelidate(validations, formData),
});
watch(
() => props.fields,
async (newFields) => {
// Reset the form
Object.keys(formData).forEach((key) => {
formData[key] = null;
});
// Create a new reactive formData and validations
formData = reactive(
newFields.reduce((acc, field) => {
acc[field.name] = null;
return acc;
}, {})
);
newFields.forEach((field) => {
set(validations, field.name, { ...field.validations });
});
// Remove the validations of the fields that no longer exist and do not match the new fields
Object.keys(validations).forEach((key) => {
if (!newFields.find((field) => field.name === key)) {
delete validations[key];
}
});
// Update the vuelidate instance with the new validations
Object.keys(validations).forEach((key) => {
set(state.v$, key, validations[key]);
});
// Reset the vuelidate instance
state.v$.$reset();
// Mark all fields as touched
state.v$.$touch();
console.log("state.v$", state.v$);
},
{ deep: true }
);
const redeemEnabled = computed(() => {
return (
props.reward.people_tour_stage_toins >= props.reward.toins ||
!props.reward.people_tour_stage?.has_redeemed
);
});
function submitForm() {
console.log("submitForm");
state.v$.$touch();
if (state.v$.$invalid || !redeemEnabled.value) {
return;
}
emit("formSubmitted", formData);
}
defineExpose({
submitForm,
});
</script>
<template>
<form @submit.prevent="submitForm">
<div class="tw-grid tw-grid-cols-1 lg:tw-grid-cols-2 tw-gap-2 lg:tw-gap-4">
<div v-for="(field, idx) in fields" :key="idx">
<label
class="tw-font-roboto tw-text-xs lg:tw-text-sm tw-text-gray-400"
:for="field.name"
>{{ field.label }}</label
>
<input
class="tw-form-input-small"
:class="state.v$[field.name]?.$error ? 'tw-border-red-350' : ''"
v-model="formData[field.name]"
:name="field.name"
:type="field.type"
/>
<div v-if="validations[field.name] && state.v$[field.name].$invalid">
<div v-for="(error, idx) in state.v$[field.name].$errors" :key="idx">
<span
class="tw-text-red-350 tw-font-roboto tw-text-xs lg:tw-text-sm"
v-if="idx === 0"
>{{ error.$message }}</span
>
</div>
</div>
</div>
</div>
</form>
</template>
<style scoped></style>