Vuelidateで親コンポーネントから渡したpropsの値に関するバリデーションが発火しなかったときの原因と解決法
概要
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