家計簿アプリを作る #6:収支の編集を実装する
家計簿アプリ作成シリーズの第6回です。今回は登録済みの収支データを編集できるようにします。
ファイル構成
src/routes/
transactions/
[id]/
edit/
+page.server.ts # 新規作成:データ取得 + 更新アクション
+page.svelte # 新規作成:編集フォーム
+page.svelte # 変更:編集ボタンを追加
+page.svelte(ルート):編集ボタンを追加する
一覧の各行に編集ボタンを追加します。編集は別ページへの遷移なので、Form Action ではなく <a> タグで実装します。
<td>
<a href={`/transactions/${transaction.id}/edit`}>編集</a>
</td>
削除はデータ変更を伴うので Form Action が適切ですが、別ページへの遷移は <a> タグで十分です。
+page.server.ts:load と update アクションを実装する
import { fail, redirect } from '@sveltejs/kit';
import type { PageServerLoad, Actions } from './$types';
import { createDb } from '$lib/server/db';
import { transactions } from '$lib/server/db/schema';
import { eq } from 'drizzle-orm';
export const load: PageServerLoad = async ({ platform, params }) => {
const db = createDb(platform!.env.DB);
const transaction = await db.select().from(transactions)
.where(eq(transactions.id, params.id))
.get();
if (!transaction) {
redirect(303, '/');
}
return { transaction };
};
export const actions: Actions = {
update: async ({ request, platform, params }) => {
const formData = await request.formData();
const amount = formData.get('amount');
const type = formData.get('type');
const category = formData.get('category');
const memo = formData.get('memo') || '';
const date = formData.get('date');
if (!amount || !type || !category || !date) {
return fail(400, { error: 'All fields except memo are required.' });
}
const db = createDb(platform!.env.DB);
await db.update(transactions)
.set({
amount: Number(amount),
type: String(type),
category: String(category),
memo: String(memo),
date: String(date)
})
.where(eq(transactions.id, params.id));
redirect(303, '/');
}
};
ポイント:
loadでparams.idを使って該当データを1件取得する。見つからない場合は/にリダイレクトupdateアクションでもparams.idを使うため、hidden input で id をフォームに持たせる必要はない- Drizzle の更新は
db.update().set({...}).where(eq(...))の構成
+page.svelte:編集フォームを作る
<script lang="ts">
import { enhance } from "$app/forms";
let { data, form } = $props();
</script>
<form method="POST" action="?/update" use:enhance>
<div>
<label for="date">日付:</label>
<input type="date" id="date" name="date" value={data.transaction.date} required />
</div>
<div>
<label for="type">種類:</label>
<select id="type" name="type" required>
<option value="income" selected={data.transaction.type === "income"}>収入</option>
<option value="expense" selected={data.transaction.type === "expense"}>支出</option>
</select>
</div>
<div>
<label for="category">カテゴリ:</label>
<input type="text" id="category" name="category" value={data.transaction.category} required />
</div>
<div>
<label for="memo">メモ:</label>
<input type="text" id="memo" name="memo" value={data.transaction.memo} />
</div>
<div>
<label for="amount">金額:</label>
<input type="number" id="amount" name="amount" value={data.transaction.amount} required />
</div>
<button type="submit">更新</button>
</form>
{#if form?.error}
<p style="color: red">{form.error}</p>
{/if}
ポイント:
- 各 input の
valueにdata.transaction.xxxを渡すことで現在の値が初期表示される <select>はselected={data.transaction.type === "income"}のように条件で選択状態を制御する
まとめ
| ポイント | 内容 |
|---|---|
| 編集ボタン | 別ページへの遷移なので <a> タグで実装 |
| データ取得 | db.select().where(eq(...)).get() で1件取得 |
| id の受け渡し | params.id で取得するため hidden input は不要 |
| 更新 | db.update().set({...}).where(eq(...)) |
| 初期値の表示 | input の value に data.transaction.xxx を渡す |
次回は認証を実装します。