🔥

Vuelidateで親コンポーネントから渡したpropsの値に関するバリデーションが発火しなかったときの原因と解決法

2022/03/12に公開

概要

Nuxt (Composition API) と Vuelidate を使っていて、配列内のオブジェクトの特定フィールドにバリデーションを書けたい場面があり、そこでハマったときの原因と解決法の記録になります。
(完全自分用メモの延長ですが、どこかでハマった人の助けになれば幸いです)

※ 実際に開発してたコードとは多少異なるので参考程度に見てください🙏

扱う値の例

type Category = {
  name: string,
}

export type Blog: {
  id: string,
  name: string,
  categories: Category[]
}

前提

入れ子になっているフォームを実装することイメージしてください 💬
( 「カテゴリーを追加」みたいなボタンを押すとフォームがニョキッと増えるようなフォーム!)

ここでは、親となるフォーム全体の部分を BlogEditFormコンポーネント として、入れ子になっている部分を子コンポーネントの Categoryコンポーネント としておきます!

(コンポーネントの分け方おかしいだろぉ!とかは思うかもですが、一旦スルーしてください😅 )

※フォームの雑イメージ

| 入力項目1[] |
| 入力項目2[] |
| カテゴリー追加ボタン |
    | カテゴリー入力フォーム1 |
    |  名前[]   |
    | カテゴリー入力フォーム2 |
    |  名前[]   |

発生した事象

親コンポーネント(フォーム全体)が持つ配列の中身を子コンポーネントに渡して、子コンポーネント(フォームの一部入力項目)内でバリデーションしたい場合にバリデーションがうまく検知されない...

↓↓↓問題があったときのコードが以下のような感じでした。

親コンポーネント:BlogEditForm

<template>
  <div>
    <!-- 中略 -->
      <div v-if="blog.categories.length > 0">
        <div
          v-for="(category, index) in blog.categories"
          :key="`category-${index}`"
        />
               <Category
               :category="category"
              @update="handleCategoryUpdate($event, index)"
            />
        </div>
      </div>
    <!-- 中略 -->
  </div>
</template>

<script lang="ts">
// 中略
import { useVuelidate } from '@vuelidate/core';
// 中略

export default defineComponent({
  name: 'BlogEditForm',
  components: {
    Category,
  },
  setup() {

    const blog = reactive<Blog>({
      id: '',
      name: '',
      categories: []
    });

    // 中略

    const handleCategoryUpdate = (
      category: CategoryType,
      index: number
    ): void => {
      const categories = [...blog.categories];
      categories[index] = category;
      blog.categories = categories;
    };
    
    // 中略

    return {
      // 中略
      handleCategoryUpdate
    };
  },
});
</script>

子コンポーネント:Category

<script lang="ts">
// 中略
import { useVuelidate } from '@vuelidate/core';

export default defineComponent({
  name: 'Category',
  props: {
    category: {
      type: Object as PropType<CategoryType>,
      required: true,
    },
  },
  setup(props, { emit }) {
    const rules = {
      category: {
        name: {
          maxLength: helpers.withMessage(
            'カテゴリー名は10文字以内で入力してください',
            maxLength(10)
          ),
        },
      },
    };
  
    const v$ = useVuelidate(rules, props.category);

    const handleCategoryName = (name: CategoryType): void => {
      const payload: CategoryType = { ...props.category, name };
      emit('update', payload);
    };
    
    return {
      v$,
      handleCategoryName,
    };
  },
});
</script>

結論

親から子へ渡した props の値は props がリアクティブ なので、 props.category のように props の中のプロパティを useVuelidate(rules, props.category); と指定してもリアクティブな値の追跡がされない模様 😇

なので、ちゃんとリアクティブな値の追跡をしたい場合は、 useVuelidate(rules, props); のように props 自体を指定するようにしましょう 🙏

refs: https://v3.ja.vuejs.org/guide/composition-api-setup.html#プロパティ

--  const v$ = useVuelidate(rules, props.category); // 正しく動かない例
++  const v$ = useVuelidate(rules, props); // 正しく動く例

const handleCategoryName = (name: CategoryType): void => {
  const payload: CategoryType = { ...props.category, name };
  emit('update', payload);
};

Discussion