Issue with Vuelidate in Vue 2.7

32 Views Asked by At

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>
0

There are 0 best solutions below