家計簿アプリを作る #42:収支一覧のカテゴリ絞り込み

家計簿アプリ作成シリーズの第42回です。これまでメモ・カテゴリのキーワード検索はできましたが、「特定のカテゴリだけ表示する」絞り込みができるようにします。

実装方針

カテゴリを選択する Select を追加し、+page.server.tsload で月・検索条件に加えてカテゴリの一致条件も monthTransactions のフィルタに加えます。

const matchCategory = selectedCategory
  ? t.category === selectedCategory
  : true;

const monthTransactions = allTransactions.filter(t => {
  const matchMonth = t.date.startsWith(selectedMonth ?? '');
  const matchSearch = search
    ? t.memo.includes(search) || t.category.includes(search)
    : true;
  return matchMonth && matchSearch && matchCategory;
});

<option> にvalueを付け忘れる

カテゴリ一覧をSelectに表示する際、最初 value を指定せずに書いてしまい、選択肢が正しく機能しませんでした。

<!-- ❌ valueが無い -->
<option>{category.name}</option>

<!-- ✅ valueを明示する -->
<option value={category.name}>{category.name}</option>

複数条件を組み合わせるとURLパラメータの持ち回しが崩れやすい

月・検索・カテゴリの3つの絞り込み条件があると、ページ送りや条件変更のたびにすべての条件をURLに引き継ぐ必要があります。最初は各操作(月変更・検索・カテゴリ変更・ページ送り)ごとにバラバラに実装してしまい、片方の条件を変更するともう片方が消えてしまうバグがありました。

// 4つの操作すべてで、同じ3パラメータを引き継ぐように統一する
goto(`/?month=${...}&page=${...}&search=${...}&category=${...}`);

$state の初期値に data を直接使う罠、再び

カテゴリ・年月選択時のURL生成で data.selectedMonth のような $state を別途用意しようとして、以前と同じ

This reference only captures the initial value of `data`.
Did you mean to reference it inside a derived instead?

の警告に何度か遭遇しました。今回学んだのは、状態が「サーバーから受け取った値をそのままイベントハンドラで使う」だけなら $state は不要で data.xxx を直接参照すればよく、「ユーザーの入力中の値を一時的に保持する必要がある」場合(検索ボックスの入力中の文字列など)だけ $state(untrack(() => data.xxx)) で初期化する、という使い分けです。

// 単にURL生成に使うだけなら$state不要
function handleMonthChange(event: Event) {
  const selectedMonth = (event.target as HTMLSelectElement).value;
  goto(`/?month=${selectedMonth}&page=1&search=${data.searchQuery}&category=${data.selectedCategory}`);
}

// 入力中の値を保持する必要がある検索ボックスだけ$state化する
let searchQuery = $state(untrack(() => data.searchQuery) ?? "");

確定していない入力値を巻き込まない設計

検索ボックスに文字を入力しただけでまだ「検索」ボタンを押していない状態で、月やカテゴリを切り替えると、未確定の検索文字列まで巻き込んでしまうのは直感に反します。そこで、検索の実行(handleSearch)だけはローカルの searchQuery(入力中の値)を使い、それ以外の操作(月変更・カテゴリ変更・ページ送り)は data.searchQuery(URLに反映済みの直近確定値)を使うように使い分けました。

まとめ

ポイント 内容
カテゴリ絞り込み 既存のフィルタ条件に matchCategory を追加するだけで自然に組み込める
<option> のvalue 明示的に指定しないと選択肢が機能しない
複数条件のURL持ち回し 全ての操作で同じパラメータ構成を維持する
$statedata URL生成に使うだけなら$state不要、入力保持が要る箇所だけuntrackで初期化する
未確定の入力値 検索実行だけローカル値を使い、他の操作は確定済みの値を使う
← トップページに戻る