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?
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):
value
-> modelValue
@input
-> @update:modelValue
<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>