Nuxt3 Form with Feedback
Tags: nuxt, nuxt3, vue, vue3
Published: 2023-07-04 13:47:03

In my [previous post](https://dev.to/michaelsynan/how-to-create-a-contact-form-using-nuxt3-and-supabase-1o4c) I demonstrated how to submit form content to [Supabase](https://supabase.com/) so you can start building your email list. Below, I will show how to add some basic validation and user feedback to help make sure the user is submitting useful information. 

---

If you're using a chrome-based browser, you can check out the Stackblitz embed, otherwise visit [here](https://stackblitz.com/edit/nuxt-starter-l3fra4?file=components%2Fcontact.vue):
{% embed https://stackblitz.com/edit/nuxt-starter-l3fra4?file=components%2Fcontact.vue %}

The final version will look like this:

![Nuxt3 form with validation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l9oicu1cbtllr3ey27ab.png)

---

This form will use two components, the signup form itself, as well as a feedback component which receives a prop from the form indicating what type of feedback to provide upon hitting the "submit" button and performing basic validation. 

&nbsp;

**Let's go.** 🏃‍♀️💨  

&nbsp; 

## Nuxt3 Template

```
<template>
  <div>
    <div class="flex items-center justify-center h-screen bg-black">
      <div
        class="text-center text-white p-6 border-2 border-white w-full max-w-md"
      >
        <h1 class="text-2xl mb-3">Join Our Newsletter</h1>
        <p class="mb-6 text-lg">Get amazing updates right to your inbox.</p>
        <form @submit.prevent="submitEmail" class="flex flex-col">
          <label class="mb-4 text-left text-stone-400">
            Name
            <input
              v-model="name"
              type="text"
              class="bg-gray-800 text-white w-full px-3 py-2 mt-2"
            />
          </label>
          <label class="mb-4 text-left text-stone-400">
            Email
            <input
              v-model="email"
              type="email"
              class="bg-gray-800 text-white w-full px-3 py-2 mt-2"
            />
          </label>
          <label class="flex items-center text-white mb-4">
            <input
              type="checkbox"
              v-model="consent"
              class="mr-3"
              id="updates"
            />
            I agree to terms and conditions
          </label>
          <button
            type="submit"
            class="bg-white text-black px-6 py-2 mt-4"
            @click.prevent="submitForm"
          >
            {{ isLoading ? 'Loading...' : 'Submit' }}
          </button>
          <feedback :formFeedback="formFeedback" />
        </form>
      </div>
    </div>
  </div>
</template>
```

**Note:** We will be using the [Nuxt Tailwind](https://tailwindcss.nuxtjs.org/) module for styling. 

The form has 3 input fields and a submit button. Each input is modeled to a ref value and the form submit button is linked to a `submitForm` function. 

Additionally, the button text is responsive to the `isLoading` value, which we will handle in the script. Finally, you can see there is the 'feedback' component which accepts the `formFeedback` ref value as a prop.
&nbsp;

## Nuxt3 Script Section

```
<script lang="ts" setup>
import { ref } from 'vue';

type FormFeedbackType = 'incomplete' | 'consent' | 'invalid' | 'success' | null;

const name = ref('');
const email = ref('');
const consent = ref(false);
const isLoading = ref(false);
const formFeedback: Ref<FormFeedbackType> = ref(null);
const success = ref(true);

const submitForm = async () => {
  isLoading.value = true;
  formFeedback.value = null;

  if (!name.value.trim() || !email.value.trim()) {
    formFeedback.value = 'incomplete';
    isLoading.value = false;
    return;
  }

  const regex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
  if (email.value && !regex.test(email.value)) {
    formFeedback.value = 'invalid';
    success.value = false;
    isLoading.value = false;
    return;
  }

  if (!consent.value) {
    formFeedback.value = 'consent';
    success.value = false;
    isLoading.value = false;
    return;
  }

  setTimeout(() => {
    // If the execution reaches here, it means that all checks have passed.
    success.value = true;
    formFeedback.value = 'success';
    isLoading.value = false;
  }, 1000);
};
</script>
```

The contact form component script includes ref values for each form field and state that is required for the form to function. Underneath the refs the async `submitForm` function which handles the validation and submission logic. In our case we are not submitting form information to an actual database so the submission status is simulated by using a `setTimeout` function.

As an bonus I created a new type for the `formFeedback` ref which only allows certain strings to get passed to the feedback component. Anything other than the options defined in the type will result in a [TypeScript](https://www.typescriptlang.org/) compile-time error.
&nbsp;

## Form Feedback Component

The feedback component accepts a single prop, named 'feedbackType', which determines the type of feedback to be displayed. Based on the value of 'feedbackType', the component renders the corresponding div. The 'feedbackType' prop can only accept specific strings, as defined by the TypeScript type associated with the formFeedback ref.

```
<script lang="ts" setup>
const props = defineProps({
  formFeedback: String,
});
</script>

<template>
  <div
    v-if="formFeedback === 'error'"
    class="text-white w-full block p-3 mt-4 bg-red-500"
  >
    There was an error processing your request.
  </div>

  <div
    v-else-if="formFeedback === 'success'"
    class="text-white w-full block p-3 mt-4 bg-green-500"
  >
    Thanks for signing up!
  </div>

  <div
    v-else-if="formFeedback === 'incomplete'"
    class="text-white w-full block p-3 mt-4 bg-red-500"
  >
    Please complete all required fields.
  </div>

  <div
    v-else-if="formFeedback === 'invalid'"
    class="text-white w-full block p-3 mt-4 bg-red-500"
  >
    Please enter a valid email address.
  </div>

  <div
    v-else-if="formFeedback === 'consent'"
    class="text-white w-full block p-3 mt-4 bg-red-500"
  >
    Please agree to terms and conditions.
  </div>
</template>

<style scoped></style>
```

This feedback component can be reused throughout your website for different forms - just make sure you update it to include different conditions. 
&nbsp;

## App.vue 

Maybe obvious but to be thorough, simply add the contact component to your `app.vue` or desired page:
```
<template>
  <div>
    <contact />
  </div>
</template>
```

---

## Conclusion

&nbsp;
> **That's it! You can now build a contact form with validation and feedback.** 🎉

&nbsp;

You can get the full code [here](https://stackblitz.com/edit/nuxt-starter-l3fra4?file=app.vue) - simply fork the project and start working on your own as needed.