家計簿アプリを作る #13:登録・編集フォームの改善

家計簿アプリ作成シリーズの第13回です。今回は収支の登録・編集フォームのカテゴリ入力をテキストから <select> に変更し、収入・支出で選択肢が切り替わるようにします。

実装方針

type(収入・支出)の選択に連動してカテゴリの選択肢を動的に切り替えます。Svelte 5 の $state$derived を使います。

Step 1:カテゴリマップと状態を定義する

const categoryMap: Record<string, string[]> = {
  income: ['給与', '副業', 'その他'],
  expense: ['食費', '交通費', '日用品', '娯楽', 'その他']
};

let selectedType = $state('income');
let categories = $derived(categoryMap[selectedType] ?? []);

selectedType が変わると categories が自動的に再計算されます。

ハマりポイント①:categoryMap の型エラー

categoryMap の型を明示しないと以下のエラーが出ます。

Element implicitly has an 'any' type because expression of type 'string'
can't be used to index type '{ income: string[]; expense: string[]; }'.

Record<string, string[]> で型を明示することで解消できます。

const categoryMap: Record<string, string[]> = { ... };

Step 2:<select>bind:value で連動させる

type<select>bind:value={selectedType} を追加します。

<select id="type" name="type" bind:value={selectedType}>
  <option value="income">収入</option>
  <option value="expense">支出</option>
</select>

カテゴリの <select>categories をループします。

<select id="category" name="category">
  {#each categories as category}
    <option value={category}>{category}</option>
  {/each}
</select>

編集フォームの対応

編集フォームでは既存データの type を初期値として渡す必要があります。

ハマりポイント②:$state の初期値に $props の値を使うとエラー

let { data, form } = $props();
let selectedType = $state(data.transaction.type); // ❌ エラー
This reference only captures the initial value of `data`.
Did you mean to reference it inside a derived instead?

letconst に変えることで解消できます。

const { data, form } = $props();
let selectedType = $state(data.transaction.type); // ✅

bind:value があれば selected 属性は不要

bind:value={selectedType} で選択状態が管理されるため、<option>selected 属性は不要です。

<!-- ❌ 不要 -->
<option value="income" selected={data.transaction.type === 'income'}>収入</option>

<!-- ✅ bind:value に任せる -->
<option value="income">収入</option>

まとめ

ポイント 内容
動的な選択肢 $state + $derivedtype の変化に連動させる
型エラー categoryMapRecord<string, string[]> を明示する
編集フォームの初期値 $props()const で受け取ることで $state の初期値に使える
selected 属性 bind:value があれば不要

次回は収支の登録・編集後に元の月フィルタ状態に戻る処理を実装します。

← トップページに戻る