Nuxt3とVuetify3を使ってフォームのバリデーションを作成する

# 完成サンプル
たとえば、未入力のまま`REGISTER(登録)`ボタンをクリックすると...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/bc9eb02d-f37c-4090-a0a5-a782e4c33961.png)

ページ上部にAlertメッセージの表示とバリデートチェックで引っかかった項目を赤字で表示するようにしてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/12690121-3fba-46df-af92-a6ed82206155.png)

# Vuetify3とは？
`Vuetify3`は、Vue3向けのUIフレームワークです。これらのUIフレームワークを使用することで統一感のあるデザインと開発工数を効率化を図ることができます。

今回は、`Alert Component`を使って実装しています。

https://vuetifyjs.com/en/components/alerts/#content

# 完成コード
まず完成したコードをお見せします。
```frontend/pages/navigation/nabigation.vue
<script setup lang="ts">
import { ref } from 'vue';
import { useField, useForm } from 'vee-validate';
import * as yup from 'yup';
import axios from 'axios';
import { useRouter } from 'vue-router';

const formRef = ref(null);
const { handleSubmit } = useForm();

const router = useRouter();

/*---Name validation---*/
const nameField = useField(
  'name',
  yup.string().required('Name must be required.').min(2, 'Please input over 1 character.')
);
const { value: name, errorMessage: nameerror } = nameField;

/*---Email validation---*/
const emailField = useField(
  'email',
  yup.string().required('Email must be required.').matches(/^[a-zA-Z0-9_\-]+(\.[a-zA-Z0-9_\-]+)*@([a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]\.)+[a-zA-Z]{2,}$/, { message: 'Mail address is invalid' })
);
const { value: email, errorMessage: emailerror } = emailField;

/*---Password validation---*/
const passwordField = useField(
  'password',
  yup.string().required('Password must be required.').min(2, 'Please input over 1 character.').max(11, 'The password is too long. Please input until 10 characters.')
);
const { value: password, errorMessage: passworderror } = passwordField;

/*---Gender validation---*/
const genderField = useField(
  'gender',
  yup.string().required('Gender must be required.')
);
const { value: gender, errorMessage: gendererror } = genderField;

/*---Survey validation---*/
const surveyField = useField<string[]>(
  'survey',
  yup.array().of(yup.string()).min(1, 'Please choose at least one.'),
  {initialValue:[]} // 配列の長さが1以上であることを確認
);
const { value: survey } = surveyField;

const nameErrorMessage = ref('');
const emailErrorMessage = ref('');
const passwordErrorMessage = ref('');
const genderErrorMessage = ref('');
const surveyErrorMessage = ref('');

/** Set alert bar on header */
const globalError = ref('');
const showAlert = ref(false);

/** Define formData validate flag */
const flagForValidate = ref(true);

const surveyOptions = [
  'I want to know from News letters', 
  'I want to know from Campaign', 
  'I want to know from Events'
];

const onSubmit = async()=>{

 try{
  /** Initialize validate flag. */
  flagForValidate.value = true;
  /** Hidden alert display */
  showAlert.value = false;
  /** Initialize error message */
  nameErrorMessage.value = '';
  emailErrorMessage.value = '';
  passwordErrorMessage.value = '';
  genderErrorMessage.value = '';
  surveyErrorMessage.value = '';

  /** Define FormData object */
  const formData = new FormData();

  /** Validate value of name if name is empty or not. */
  if(name.value === '' || name.value === undefined){
    nameErrorMessage.value = 'Please input your name.';
    /** Update value of flag */
    flagForValidate.value = false;
  }
  formData.append('name',name.value);

  /** Validate value of email if email is empty or not. */
  if(email.value === '' || email.value === undefined){
    emailErrorMessage.value = 'Please input your email.';
    /** Update value of flag */
    flagForValidate.value = false;
  }
  formData.append('email',email.value);

  /** Validate value of password if password is empty or not. */
  if(password.value === '' || password.value === undefined){
    passwordErrorMessage.value = 'Please input your password.';
    /** Update value of flag */
    flagForValidate.value = false;
  }
  formData.append('password',password.value);

  /** Validate value of gender if gender is empty or not. */
  if(gender.value === '' || gender.value === undefined){
    genderErrorMessage.value = 'Please input your gender.';
    /** Update value of flag */
    flagForValidate.value = false;
  }
  formData.append('gender',gender.value);

  /** Validate value of gender if gender is empty or not. */
  if(survey.value.length === 0 || survey.value === undefined){
    surveyErrorMessage.value = 'Please input your survey.';
    /** Update value of flag */
    flagForValidate.value = false;
  }

  /** Evaluate value of flag:if this is false,show alert message on header.*/
  if(!flagForValidate.value){
    showAlert.value = true;
    globalError.value = 'Invalide some values on the form.Please confirm and input data.';
    return;
  }

  formData.append('survey',JSON.stringify(survey.value));
  console.log('survey.value:', survey.value);
  console.log(...formData.entries());
  
  const response = await axios.post('http://localhost:8000/api/creategeneraluser/',formData);
  if(response.status === 201){
    alert('Sucess');
    console.log(response);
    router.push('/dashboard/dashboard');
  }else{
    alert('>>');
    
  }
 }catch(error){
  alert('error');
  console.log(error);
 }

}

//const router = useRouter();
const clickBackToTopPage = () => {
  router.push('/');
};
</script>

<template>
  <v-app>
    <v-alert
    v-if="showAlert"
    :text="globalError"
    type="error"
    title="Invalide Error"
    class="mt-2 mb-4"
    border="start"
    variant="tonal"
    color="error"
    icon="$error"
    prominent
  ></v-alert>
    <v-main>
      <v-container class="mt-5">
        <v-form @submit.prevent="onSubmit" ref="formRef">
          <!-- Name -->
          <v-text-field
            v-model="name"
            label="Name"
            required
          ></v-text-field>
          <span>{{ nameerror }}</span>
          <span style="color:red;">{{ nameErrorMessage }}</span>

          <!-- Mail Address -->
          <v-text-field
            v-model="email"
            label="Email"
            type="email"
            required
          ></v-text-field>
          <p>{{ emailerror }}</p>
          <span style="color:red;">{{ emailErrorMessage }}</span>

          <!-- Password -->
          <v-text-field
            v-model="password"
            label="Password"
            type="password"
            required
          ></v-text-field>
          <p>{{ passworderror }}</p>
          <span style="color:red;">{{ passwordErrorMessage }}</span>

          <!-- Gender (radio button) -->
          <v-radio-group
            v-model="gender"
            label="Sex"
            row
          >
            <v-radio label="man" value="man"></v-radio>
            <v-radio label="woman" value="woman"></v-radio>
            <v-radio label="other" value="other"></v-radio>
          </v-radio-group>
          <p>{{ gendererror }}</p>
          <span style="color:red;">{{ genderErrorMessage }}</span>
          <br/>
          <!-- Survey (checkboxes) -->
          <v-label>Survey</v-label>
          <v-row>
            <v-col cols="12" v-for="option in surveyOptions" :key="option">
              <v-checkbox
                :label="option"
                :value="option"
                v-model="survey"
                hide-details
              />
            </v-col>
          </v-row>
          <span style="color:red;">{{ surveyErrorMessage }}</span>
          <br/>
          <!--
          <v-selection-group v-model="survey" multiple>
            <v-checkbox
              v-for="option in surveyOptions"
              :key="option"
              :label="option"
              :value="option">
            </v-checkbox >
          </v-selection-group>
          <p>{{ surveyerror }}</p>
          -->

          <!-- Submit button -->
          <v-btn type="submit" color="primary" class="mr-4">Register</v-btn>
          <v-btn type="button" color="success" @click="clickBackToTopPage">BackToTopPage</v-btn>
        </v-form>
      </v-container>
    </v-main>
  </v-app>
</template>

```

# コードの説明

### `Yup`の導入
`Yup`は、バリデーションチェックを行うためのライブラリです。

https://www.npmjs.com/package/yup

`Yup`を使えば、バリデーションのカスタマイズを柔軟に行うことができます。


```sample.vue
<script setup lang="ts">
import * as yup from 'yup';

/*---Name validation---*/
const nameField = useField(
  'name',
  yup.string().required('Name must be required.').min(2, 'Please input over 1 character.')
);
const { value: name, errorMessage: nameerror } = nameField;
</script>
```

### `Vuetify3-Alert Components`の導入
今回は、`Register`ボタンを押下してエラーを検知した時にページ上部にAlertメッセージを表示させます。
そのため、`v-alert`コンポーネントを使ってオブジェクトを作成しましょう。

```sample.vue
<script setup lang="ts">
//Set alert bar on header 
const globalError = ref('');
const showAlert = ref(false);
</script>

    <v-alert
    v-if="showAlert"
    :text="globalError"
    type="error"
    title="Invalide Error"
    class="mt-2 mb-4"
    border="start"
    variant="tonal"
    color="error"
    icon="$error"
    prominent
  ></v-alert>
```

`const globalError = ref('');`は、Alertメッセージの内容を埋め込むためのオブジェクトです。
`const showAlert = ref(false);`は、`v-alert`をBoolean値に応じて動的に制御するオブジェクトです。

### 各項目が空の場合のエラー表示
たとえば、`v-model`の摂る双方向ばいディング値が`name`の値が空の場合に`<span />`タグにエラーメッセージを表示する方法です。


```sample.vue
<script setup lang="ts">
const nameErrorMessage = ref('');

/** Validate value of name if name is empty or not. */
  if(name.value === '' || name.value === undefined){
    nameErrorMessage.value = 'Please input your name.';
    /** Update value of flag */
    flagForValidate.value = false;
  }
  formData.append('name',name.value);
</script>
```

`ref`使って初期化('')します。
バリデーションエラーがった場合は、メッセージを`nameErrorMessage `に入れるのですが上記のように入れます。
```sample.vue
<script setup lang="ts">
nameErrorMessage.value = 'Please input your name.';
</script>
```

誤って`nameErrorMessage = 'Please input your name.';`としてしまうと直接文字列を代入 すると、Vue のリアクティビティが壊れます。これは ref オブジェクト自体を上書きしているためです。

以上です。