はじめに
この記事は、Laravel × Vue3(Inertia.js)で家計簿アプリを開発中に遭遇した問題と、その解決策を備忘録としてまとめたものです。
今回のゴール
- 支出登録・削除時に一覧(ExpenseList.vue)を自動で更新する
- 合計支出金額(
formattedTotal)をリアルタイムで更新する
1. 支出一覧のリアルタイム更新
問題
-
Dashboard.vueで<ExpenseList ref="expenseListRef" ... >としているため、を呼べば一覧は再取得できるはずでした。expenseListRef.value.reloadExpenses(); - しかし、Vue3の
<scripst setup>でref経由で子コンポーネントのメソッドを呼び出す際、refがnullになるケースがあり、タイミングによってはうまく動かないことがありました。
解決策
ExpenseList.vue側でdefineExposeを使ってreloadExpensesを公開済みなので、props経由で状態を監視する方法に切り替えます。
ExpenseList.vue
watch(
() => props.refreshKey,
() => {
reloadExpenses();
}
);
ポイント
- 親から
refreshKey.value++とするだけで、自動的にreloadExpenses()が呼ばれ、一覧が更新されます。 - 親コンポーネントはリファレンス経由でメソッドを呼ぶ必要がなくなり、シンプルになります。
2. 合計支出合計(formattedTotal)のリアルタイム更新
一覧は更新できても、合計金額{{ formattedTotal }}はリロードしないと更新されないのが課題でした。
方針
-
props.totalExpenseも依存しているため、リアクティブな変数に置き換える。 - APIで最新の合計金額を取得し、支出登録や削除後に反映されます。
修正手順
- 合計支出用のリアクティブ変数を定義
const totalExpense = ref(Number(props.totalExpense)); const formattedTotal = computed(() => { return totalExpense.value.toLocaleString(); }); - APIで最新の合計支出を取得する関数を追加
const fetchTotalExpense = async () => { try { const response = await axios.get(route('expenses.total')); totalExpense.value = Number(response.data.total); } catch (e) { console.error('合計支出の取得エラー', e); } }; - フォーム送信・削除後に合計金額を再取得
- 支出登録後
const submit = () => { form.post(route('expenses.store'), { onSuccess: () => { form.reset(); refreshKey.value++; fetchTotalExpense(); // ← ここで再取得! }, }); };- 削除後
<ExpenseList :ref="expenseListRef" :initial-expenses="props.expenses" :refresh-key="refreshKey" @expenses-updated="handleExpensesUpdated" />const handleExpensesUpdated = () => { refreshKey.value++; fetchTotalExpense(); // ← 削除後も再取得 }; - バックエンドに合計取得APIを追加
DashboardController.phppublic function total() { $now = Carbon::now(); $userId = Auth::id(); $total = Expense::whereYear('date', $now->year) ->where('user_id', $userId) ->whereMonth('date', $now->month) ->sum('amount'); return response()->json(['total' => $total]); } - ルート設定
web.php
Route::get('/expenses/total', [DashboardController::class, 'total'])->name('expenses.total');
更新が必要な箇所まとめ
| 項目 | 対応内容 |
|---|---|
formatterTotal |
ref経由でリアクティブ化 |
| 入力成功時 |
fetchTotalExpense()で最新の合計を取得 |
| 削除成功時 | 同上 |
| バックエンド | 合計支出を返すAPIルートを追加 |
3. formattedTotalの仕組みを復習
元々の定義はこうなっていました:
const formattedTotal = computed(() => {
return Number(props.totalExpense).toLocaleString();
});
つまり、props.totalExpenseをフォーマットしているだけです。
props.totalExpenseの中身はどこから?
Dashboard Controller.php
return Inertia::render('Dashboard', [
'expenses' => $expenses,
'category' => $categories,
'flash' => [
'message' => session('message')
],
'totalExpense' => $totalExpense,
]);
ここで渡している$totalExpenseは、以下のクエリで取得した「今月の合計支出」です:
$totalExpense = Expense::whereYear('date', $now->year)
->where('user_id', $userId)
->whereMonth('date', $now->month)
->sum('amount');
⚠️注意点
props,totalExpenseは初回レンダリング時の値なので。
登録や削除をしても自動では更新されません。
そのため今回のように、
- フロントで
ref管理をする - APIで最新値を取得する
という実装が必要になります。
まとめ
| 対応 | 内容 |
|---|---|
| 一覧更新 |
refreshKeyをwatchして自動でreloadExpenses
|
| 合計更新 | APIで再取得+refでリアクティブ化 |
| バックエンド | 合計金額返却APIを追加 |
この対応によって、支出一覧と合計金額がリアルタイムで同期されるようになりました。