家計簿アプリを作る #18:フォームのバリデーション強化

家計簿アプリ作成シリーズの第18回です。今回は収支の登録・編集フォームにバリデーションを実装します。

実装方針

SvelteKit の Form Actions では fail() で返したデータを +page.svelteform プロパティで受け取れます。複数フィールドのエラーをまとめて返すには errors オブジェクトを使います。また values で入力値を返すことで、バリデーションエラー後も入力値がリセットされずに残ります。

バリデーション内容

  • 金額:必須、かつ1以上
  • 種類:必須
  • カテゴリ:必須
  • 日付:必須

Step 1:+page.server.ts にバリデーションを追加する

const errors: Record<string, string> = {};

// 金額は必須、かつ1以上
if (!amount) {
  errors.amount = '金額を入力してください';
} else if (Number(amount) <= 0) {
  errors.amount = '金額は1以上を入力してください';
}
// 種類は必須
if (!type) {
  errors.type = '種類を入力してください';
}
// カテゴリは必須
if (!category) {
  errors.category = 'カテゴリを入力してください';
}
// 日付は必須
if (!date) {
  errors.date = '日付を入力してください';
}

if (Object.keys(errors).length > 0) {
  return fail(400, {
    errors,
    values: { amount, type, category, memo, date }
  });
}

各フィールドのエラーを errors オブジェクトに詰めていき、最後にまとめて fail() で返します。

ハマりポイント:金額チェックの順序

金額の必須チェックと1以上チェックを別々の if で書くと、後のチェックが前のエラーを上書きします。

// ❌ 空のとき「1以上」のエラーが「必須」で上書きされる
if (Number(amount) <= 0) {
  errors.amount = '金額は1以上を入力してください';
}
if (!amount) {
  errors.amount = '金額を入力してください';
}

// ✅ else if で排他的にチェックする
if (!amount) {
  errors.amount = '金額を入力してください';
} else if (Number(amount) <= 0) {
  errors.amount = '金額は1以上を入力してください';
}

Step 2:+page.svelte でエラーを表示する

各フィールドの直下にエラーメッセージを表示します。

<div>
  <label for="amount">金額</label>
  <input
    type="number"
    id="amount"
    name="amount"
    value={form?.values?.amount ?? ""}
    required
  />
  {#if form?.errors?.amount}
    <p class="mt-1 text-sm text-fg-danger-strong">{form.errors.amount}</p>
  {/if}
</div>

value={form?.values?.amount ?? ""} でバリデーションエラー後も入力値を保持します。

まとめ

ポイント 内容
複数エラーのまとめ返し errors オブジェクトに詰めて fail() で一括返却
入力値の保持 valuesfail() に含めて form?.values で参照
チェックの順序 必須チェックを先に、範囲チェックは else if
エラー表示 フィールドの直下に {#if form?.errors?.xxx} で表示
← トップページに戻る