javascriptvue.jsvuejs3v-model

Vue 3 Option api: Parent element not receiving v-model input from child component


I'm working on a Nuxt 3 project and facing an issue with a contact form where the parent element doesn't seem to be receiving the input values from a child component. Here's a breakdown of my code:

Parent Component:

<script setup>
    import { ref } from 'vue';
    const formElement = ref(null);

    const errorMessage = ref(null);
    const form = ref({
        name: null,
        subject: null,
        email: null,
        message: null
    });

    const submitForm = () => {
        if (!form.value.name || !form.value.subject || !form.value.message) {
            console.log(form.value);
            errorMessage.value = "please make sure your 'fullname', 'subject', and 'message' fields are filled";
        } else if (!/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(form.value.email)) {
            errorMessage.value = "please fill in a valid 'email'";
        } else if (form.value.message.length < 3) {
            errorMessage.value = "please add some more context to the 'message'";
        } else {
            console.log("button clicked");
            console.log(form.value);
            formSubmit();
        }
    };

    const formSubmit = () => {
        const formElement = document.querySelector('form');
        console.log(formElement);
        // formElement.submit();
    };
</script>

<template>
<form action="https://formsubmit.co/info@example.co.uk" method="POST" ref="formm">
        <UtilityMainInput name="Name" placeholder="Full Name" inputType="text" controlType="input" v-model="form.name"/>
    <UtilityMainInput name="Subject" placeholder="Subject" inputType="text" controlType="input" v-model="form.subject"/>
    <UtilityMainInput name="Email" placeholder="Email Address" inputType="email" controlType="input" v-model="form.email"/>
    <UtilityMainInput name="Message" placeholder="Tell us anything" inputType="textarea" controlType="textarea" v-model="form.message"/>
    <div class="empty-height"></div>
    <UtilityButton type="btn" size="medium" :onClick="submitForm">Send Message</UtilityButton>
</form>
</template>

Child Component (UtilityMainInput):

<template>
    <div class="normal-form">
        <label :for="name" class="label">
            {{name}}
        </label>

        <!-- text input -->
        <input 
            v-if="controlType === 'input' && inputType ==='text'" 
            type="text" 
            maxlength="50"
            :name="name" 
            :value="value" 
            @input="$emit('input', $event.target.value) ">
        <!-- email input -->
        <input 
            v-if="controlType === 'input' && inputType ==='email'" 
            type="email" 
            :name="name" 
            :value="value" 
            @input="$emit('input', $event.target.value) ">
        <!-- textarea input -->
        <div class="textarea" v-if="controlType === 'textarea'">
            <textarea  
                :name="name" 
                :value="value"
                @input="$emit('input', $event.target.value) ">
            </textarea>
        </div>
    </div>
</template>


<script>
export default {
    props: {
        name: {
            type: String,
            required: true
        },
        placeholder: {
            type: String,
            required: false,
            default: "text goes here"
        },
        inputType: {
            type: String,
            required: false,
            default: "text"
        },
        controlType: {
            type: String,
            required: false,
            default: 'input'
        },
        value: {
            type: String,
            default: ''
        }
    },
}
</script>

When I fill in the form and click submit, I get null for all values in the parent component's data properties, even though I've used v-model to bind them. Can someone help identify why the parent is not updating with the child's input values?


Solution

  • There is this breaking change in Vue 3 that you should follow in Option api. for more info read official docs as well.

    Vue 2 version:

    <input
      :value="name"
      @input="name= $event.target.value"
    />
    

    Vue 3 version (Both Option api and Composition api):

    <CustomInput
      :model-value="name"
      @update:model-value="newValue => name = newValue"
    />
    

    The updated code to your files is as follows: check it in Vue playground if you want

    <script setup>
    import { ref } from 'vue';
    import UtilityMainInput from './UtilityMainInput.vue';
    const formElement = ref(null);
    
    const errorMessage = ref(null);
    const form = ref({
      name: null,
      subject: null,
      email: null,
      message: null
    });
    
    const submitForm = () => {
      if (!form.value.name || !form.value.subject || !form.value.message) {
        console.log(form.value);
        errorMessage.value = "please make sure your 'fullname', 'subject', and 'message' fields are filled";
      } else if (!/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(form.value.email)) {
        errorMessage.value = "please fill in a valid 'email'";
      } else if (form.value.message.length < 3) {
        errorMessage.value = "please add some more context to the 'message'";
      } else {
        console.log("button clicked");
        console.log(form.value);
        formSubmit();
      }
    };
    
    const formSubmit = () => {
      const formElement = document.querySelector('form');
      console.log(formElement);
      // formElement.submit();
    };
    
    function test(e) {
      console.log(e)
    }
    </script>
    
    <template>
      <form action="https://formsubmit.co/info@example.co.uk" method="POST" ref="formm">
        <UtilityMainInput name="Name" placeholder="Full Name" inputType="text" controlType="input" v-model="form.name" />
        <UtilityMainInput name="Subject" placeholder="Subject" inputType="text" controlType="input" v-model="form.subject" />
        <UtilityMainInput name="Email" placeholder="Email Address" inputType="email" controlType="input"
          v-model="form.email" />
        <UtilityMainInput name="Message" placeholder="Tell us anything" inputType="textarea" controlType="textarea"
          v-model="form.message" />
        <div class="empty-height"></div>
        <button @click="submitForm">Send Message</button>
      </form>
    </template>
    
    <template>
        <div class="normal-form">
            <label :for="name" class="label">
                {{ name }}
            </label>
            <!-- text input -->
            <input v-if="controlType === 'input' && inputType === 'text'" type="text" maxlength="50" :name="name"
                :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
            <!-- email input -->
            <input v-if="controlType === 'input' && inputType === 'email'" type="email" :name="name" :value="modelValue"
                @input="$emit('update:modelValue', $event.target.value)">
            <!-- textarea input -->
            <div class="textarea" v-if="controlType === 'textarea'">
                <textarea :name="name" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
                </textarea>
            </div>
        </div>
    </template>
    
    
    <script>
    export default {
        emits: ['update:modelValue'],
        props: {
            name: {
                type: String,
                required: true
            },
            placeholder: {
                type: String,
                required: false,
                default: "text goes here"
            },
            inputType: {
                type: String,
                required: false,
                default: "text"
            },
            controlType: {
                type: String,
                required: false,
                default: 'input'
            },
            modelValue: {
                type: String,
                default: ''
            }
        }
    }
    </script>