Validate form input fields in child component from a parent component with Vuelidate
Asked Answered
T

4

6

I am new to Vue Js and Vuelidate. Just tried to validate form input fields from a parent component like here: https://github.com/monterail/vuelidate/issues/333

Child component in the parent:

<contact-list ref="contactList" :contacts="contacts" @primaryChanged="setPrimary" @remove="removeContact" @ready="isReady => readyToSubmit = isReady"/>

The method in the child:

computed: {
    ready() {
        return !this.$v.email.$invalid;
    }
},
watch: {
    ready(val) {
        this.$emit('ready', val);
    }
},

methods: {
    touch() {
        this.$v.email.$touch();
    }
}

I'm calling the touch() method from the parent like so:

submit() {
            this.$refs.contactList.touch();
        },

But I get this error:

Error in event handler for "click": "TypeError: this.$refs.contactList.touch is not a function".

Any ideas? Thanks.

Tillage answered 5/12, 2018 at 8:55 Comment(1)
seems alright. Are you trying to call it before contact-list is created?Codon
T
-3

I have found another solution for this validation, it's very simple. Child component in the parent:

<contact-list ref="customerContacts" :contacts="customer.contacts" />

Validations in child component:

:validator="$v.model.$each[index].name
...
validations: {
    model: {
        required,
        $each: {
            name: {
                required
            },
            email: {
                required,
                email
            },
            phone: {
                required
            }
        }

    }

}

And on submit in the parent:

async onSubmit() {
            if(this.$refs.customerContacts.valid())
...
Tillage answered 18/12, 2018 at 8:52 Comment(4)
Sorry could you explain it with some more details? I don't understand this example.. Maybe I'm stupid today...Widmer
Would it be possible to expand (and explain) your code a bit?Bourse
where do you get .valid() functionality?Aglitter
create a method in the child component called valid and then make it return this.$v.$invalidMirepoix
F
1

I was facing the same problem. Here is what I have done to solve it.

  1. Created a global event pool. Where I can emit events using $emit and my child can subscribe using $on or $once and unsubscribe using $off. Inside your app.js paste the below code. Below is the list of event pool actions.

    • Emit: this.$eventPool.$emit()
    • On: this.$eventPool.$on()
    • Off: this.$eventPool.$off()
    • once: this.$eventPool.$once()

Vue.prototype.$eventPool = new Vue();

  1. Inside my child components, I have created a watch on $v as below. Which emits the status of the form to the parent component.
watch: {
    "$v.$invalid": function() {
      this.$emit("returnStatusToParent", this.$v.$invalid);
    }
  }
  1. Now inside you parent component handle the status as below.

<ChildComponent @returnStatusToParent="formStatus =>isChildReady=formStatus"></ChildComponent>

  1. Now to display the proper errors to the users we will $touch the child form. For that, we need to emit an event in the above-created event pool and our child will subscribe to that.

parent:

this.$eventPool.$emit("touchChildForm");

child:

 mounted() {
    this.$eventPool.$on("touchChildForm", () => {
      this.$v.$touch();
      this.$emit("returnStatusToParent", this.$v.$invalid);
    });
  },
  destroyed() {
    this.$eventPool.$off("touchChildForm", () => `{});`
  }

Hope it helps :)

Feat answered 28/3, 2020 at 2:31 Comment(0)
C
1

I'm adding my answer after this question already has an accepted solution, but still hope it might help others. I have been at this for the entire week. None of the above solutions work for my scenario because there are children components nested 2 levels deep so the "ref" approach won't work when I need the utmost parent component to trigger all validations and be able to know if the form is valid.

In the end, I used vuex with a fairly straightforward messages module. Here is that module:

const state = {
  displayMessages: [],
  validators: []
};

const getters = {
  getDisplayMessages: state => state.displayMessages,
  getValidators: state => state.validators
};

const actions = {};

const mutations = {
  addDisplayMessage: (state, message) => state.displayMessages.push(message),
  addValidator: (state, validator) => {
    var index = 0;
    while (index !== -1) {
      index = _.findIndex(state.validators, searchValidator => {
        return (
          searchValidator.name == validator.name &&
          searchValidator.id == validator.id
        );
      });
      if (index !== -1) {
        console.log(state.validators[index]);
        state.validators.splice(index, 1);
      }
    }

    state.validators.push(validator);
  }
};

export default {
  state,
  getters,
  actions,
  mutations
};

Then each component has this in its mounted event:

  mounted() {
    this.addValidator( {name: "<name>", id: 'Home', validator: this.$v}) ;
  }

Now when a user clicks the "Submit" button on the home page, I can trigger all validations like so:

  this.getValidators.forEach( (v) => {
    console.log(v);
    v.validator.$touch();
  });

I can just as easily check the $error, $invalid properties of the vuelidate objects. Based on my testing, the vuelidate reactivity remains intact so even though the objects are saved to vuex, any changes on the component fields are reflected as expected.

I plan to leave the messages and styling to convey the errors in the gui to the components themselves, but this approach lets me pause the form submission when an error occurs.

Is this a good thing to do? I honestly have no idea. The only hokey bit if having to remove validators before adding them. I think that's more an issue with my component logic, than an issue with this as a validation solution.

Given that this has taken me a whole week to arrive at, I'm more than happy with the solution, but would welcome any feedback.

Colwell answered 18/12, 2020 at 22:55 Comment(0)
C
0

Had a similar issue trying to validate child components during a form submission on the parent component. My child components are only one level deep so if you have deeper nesting this way may not work or you have to check recursively or something. There are probably better ways to check but this worked for me. Good luck.

// parent component
  methods: {
    onSave() {
      let formIsInvalid = this.$children.some(comp => {
        if (comp.$v) { // make sure the child has a validations prop
          return comp.$v.$invalid
        }
      })

      if (!formIsInvalid) {          
        // submit data
      }          
      else {
        // handle invalid form
      }
   }
Chelsea answered 17/10, 2019 at 21:57 Comment(0)
T
-3

I have found another solution for this validation, it's very simple. Child component in the parent:

<contact-list ref="customerContacts" :contacts="customer.contacts" />

Validations in child component:

:validator="$v.model.$each[index].name
...
validations: {
    model: {
        required,
        $each: {
            name: {
                required
            },
            email: {
                required,
                email
            },
            phone: {
                required
            }
        }

    }

}

And on submit in the parent:

async onSubmit() {
            if(this.$refs.customerContacts.valid())
...
Tillage answered 18/12, 2018 at 8:52 Comment(4)
Sorry could you explain it with some more details? I don't understand this example.. Maybe I'm stupid today...Widmer
Would it be possible to expand (and explain) your code a bit?Bourse
where do you get .valid() functionality?Aglitter
create a method in the child component called valid and then make it return this.$v.$invalidMirepoix

© 2022 - 2024 — McMap. All rights reserved.