0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vuetifyでフォーカスアウト時、データを取得した後にバリデーションを実行する方法

Posted at

はじめに

Vuetifyを使用していて、後述のユースケースがあり、バリデーションのタイミングの制御方法を調査したので記事にします。

ユースケース

  • テキストを入力して、フォーカスアウトした時、以下を実行する
    • APIでデータを取得する
    • バリデーションで入力したテキストがAPIで取得したデータの中にあるか確認する(なければエラーとする)

問題点

順番としては、APIでデータを取得した後、バリデーションが実行されることが理想なのですが、下記のコードでは、以下の二つのイベントが同時に実行されるので必ずバリデーションエラーになります。

  • @Blur="handleBlur"(データを取得するためのフォーカスアウトのイベント)
  • :rules="textRules"(バリデーション)

1.gif

詳細に書くと以下のようになります

  • TextFieldにapple(appleはhandleBlurで取得するデータリストの中に含まれるデータ)を入力し、フォーカスアウト
  • handleBlurが先に実行、その後、handleBlurの終了を待たずにrulesが実行
  • handleBlurでデータを取得中に、rulesでバリデーションされて、データがないのでエラーになる
  • handleBlurが完了し、appleを含むデータが取得される
<template>
  <v-app>
    <v-container>
      <v-text-field
        v-model="msg"
        @blur="handleBlur"
        :rules="textRules"
        validate-on="lazy blur"
      />
      <!-- ログ用のテキスト -->
      <div style="white-space: pre-wrap; word-wrap: break-word">
        {{ msgListLabel }}
      </div>
    </v-container>
  </v-app>
</template>

<script setup>
  import { ref, computed } from 'vue'

  // TextFieldのモデル
  const msg = ref('')
  
  // バリデーション
  const textRules = [
    (v) => {
      msgList.value.push("rules start");
      if(dataList.value.includes(v)){
        return true
      }

      return "入力したフルーツはありません";
    }
  ];

  // 入力できるデータリスト(fetchDataで取得する)
  const dataList = ref([]);

  // ログとして表示するようの文字配列
  const msgList = ref([])
  const msgListLabel = computed(
    () => msgList.value.join('\n')
  )

  // TextFieldからフォーカスアウトした時のイベント
  const handleBlur = async () => {
    msgList.value.push("handleBlur Start");
    const data = await fetchData();
    dataList.value = data;
    msgList.value.push("fetched data");
    msgList.value.push("handleBlur End");
  }

  // 擬似的なAPIからデータを取得する処理 
  const fetchData = async () => {
    await sleep(1000);
    return ["apple", "banana", "cherry"]
  };

  const sleep = (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
</script>

デモ

改善

改善ポイントの解説は下記です

  • v-text-fieldのバリデーションのタイミングvalidate-onをlazy submitに変更して、フォーカスアウト時はバリデーションしないようにする
  • v-text-fieldにrefを渡しておく
  • handleBlurの中で、refを使ってバリデーションを実行する
<template>
  <v-app>
    <v-container>
      <v-text-field
+       ref="textFieldRef"
        v-model="msg"
        @blur="handleBlur"
        :rules="textRules"
-       validate-on="lazy blur"
+       validate-on="lazy submit"
      />
      <!-- ログ用のテキスト -->
      <div style="white-space: pre-wrap; word-wrap: break-word">
        {{ msgListLabel }}
      </div>
    </v-container>
  </v-app>
</template>

<script setup>
  import { ref, computed } from 'vue'

+ const textFieldRef = ref()

  // TextFieldのモデル
  const msg = ref('')
  
  // バリデーション
  const textRules = [
    (v) => {
      msgList.value.push("rules start");
      if(dataList.value.includes(v)){
        return true
      }

      return "入力したフルーツはありません";
    }
  ];

  // 入力できるデータリスト(fetchDataで取得する)
  const dataList = ref([]);

  // ログとして表示するようの文字配列
  const msgList = ref([])
  const msgListLabel = computed(
    () => msgList.value.join('\n')
  )

  // TextFieldからフォーカスアウトした時のイベント
  const handleBlur = async () => {
    msgList.value.push("handleBlur Start");
    const data = await fetchData();
    dataList.value = data;
    msgList.value.push("fetched data");
+   textFieldRef.value?.validate()
    msgList.value.push("handleBlur End");
  }

  // 擬似的なAPIからデータを取得する処理 
  const fetchData = async () => {
    await sleep(1000);
    return ["apple", "banana", "cherry"]
  };

  const sleep = (ms) => {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
</script>

改良版

2.gif

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?