bCliks
bCliks

Reputation: 2958

How to communicate/ validate Component inside form in vuejs?

I have a form which contains name and address components. In the parent's page I have a submit button. I can send data from the parent to the children using props. Now I am trying to get the children's values from the parent's form. And I want to validate child fields from the parent's form. How to acheive this?

Here is my form structure.

parent.vue

<form @submit.prevent="handleSubmit">
    <name-fields :user='user'></name-fields>
    <address-fields :address='address'></address-fields>

    <button>Register</button>
</form>

<script>
export default {
    data () {
        return {
            user: {
                firstName: 'Raja',
                lastName: 'Roja',
            },
            address: {
                doorNo: '11',
                state: 'KL',
                country: 'IN',
            },
            submitted: false
        }
    },
    components: {
        'name-fields': cNameFields,    
        'address-fields': cAddressFields,
    },
}
</script>

cNameFields.vue

<template>
  <div>
      <div class="form-group">
        <label for="firstName">First Name</label>
        <input type="text" v-model="user.firstName" v-validate="'required'" name="firstName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('firstName') }" />
        <div v-if="submitted && errors.has('firstName')" class="invalid-feedback">{{ errors.first('firstName') }}</div>
    </div>
    <div class="form-group">
        <label for="lastName">Last Name</label>
        <input type="text" v-model="user.lastName" v-validate="'required'" name="lastName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('lastName') }" />
        <div v-if="submitted && errors.has('lastName')" class="invalid-feedback">{{ errors.first('lastName') }}</div>
    </div> 
    </div>
</template>

<script>
export default {
  name: 'name',
  props: {
            user: Object,
            submitted : Boolean
        },
</script>

Currently getting this output: Currently getting this output:

What I want to do:

What I want to do:

Upvotes: 0

Views: 2966

Answers (2)

Husam Elbashir
Husam Elbashir

Reputation: 7177

You are passing objects as props to your children, which are passed by reference in JavaScript. From the Vue docs ..

Note that objects and arrays in JavaScript are passed by reference, so if the prop is an array or object, mutating the object or array itself inside the child component will affect parent state.

This means that you're already getting the children's values in the parent and you can access them in the parent through this.user.firstName,this.user.lastName, this.address.doorNo, etc. If this isn't the intended behavior and you want to keep your parent's data isolated then you should look into deep cloning your objects.

If you want to expose your validation errors from your child components to the parent you can look into Scoped Slots. So you may do something like this ..

parent.vue

<form @submit.prevent="handleSubmit">
    <name-fields :user='user'>
        <span slot="firstName" slot-scope="{validationErrors}" style="color:red">
            {{validationErrors.first('firstName')}}
        </span>
        <span slot="lastName" slot-scope="{validationErrors}" style="color:red">
            {{validationErrors.first('lastName')}}
        </span>
    </name-fields>
    
    <address-fields :address='address'>
        <span slot="doorNo" slot-scope="{validationErrors}" style="color:red">
            {{validationErrors.first('doorNo')}}
        </span>
        <span slot="state" slot-scope="{validationErrors}" style="color:red">
            {{validationErrors.first('state')}}
        </span>
        <span slot="country" slot-scope="{validationErrors}" style="color:red">
            {{validationErrors.first('country')}}
        </span>
    </address-fields>
    
    <button>Register</button>
</form>

cNameFields.vue

<template>
    <div>
        <div class="form-group">
            <label for="firstName">First Name</label>
            <input type="text" v-model="user.firstName" v-validate="'required'" name="firstName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('firstName') }" />
            <slot name="firstName" :validationErrors="errors"></slot>
        </div>
        <div class="form-group">
            <label for="lastName">Last Name</label>
            <input type="text" v-model="user.lastName" v-validate="'required'" name="lastName" class="form-control" :class="{ 'is-invalid': submitted && errors.has('lastName') }" />
            <slot name="lastName" :validationErrors="errors"></slot>
        </div> 
    </div>
</template>

This is a great video that explains how scoped slots work.

Upvotes: 1

Indra Kusuma
Indra Kusuma

Reputation: 31

Use this.$emit https://v2.vuejs.org/v2/api/#vm-emit and also use watch https://v2.vuejs.org/v2/api/#vm-watch

So in child component you should watch for changes in user.firstName and user.lastName. Call emit in the watch and get the value in parent. Don't forget to also emit the this.errors bag which comes from vee-validate.

Hope this will help you :)

Upvotes: 1

Related Questions