1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Laravel+Vue】支出一覧と合計金額をリアルタイム更新する方法【家計簿アプリ開発メモ】

1
Posted at

はじめに

この記事は、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で最新の合計金額を取得し、支出登録や削除後に反映されます。

修正手順

  1. 合計支出用のリアクティブ変数を定義
    const totalExpense = ref(Number(props.totalExpense));
    const formattedTotal = computed(() => {
     return totalExpense.value.toLocaleString();
    });
    
    
  2. APIで最新の合計支出を取得する関数を追加
    const fetchTotalExpense = async () => {
      try {
        const response = await axios.get(route('expenses.total'));
        totalExpense.value = Number(response.data.total);
      } catch (e) {
        console.error('合計支出の取得エラー', e);
      }
    };
    
    
  3. フォーム送信・削除後に合計金額を再取得
    • 支出登録後
    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(); // ← 削除後も再取得
    };
    
  4. バックエンドに合計取得APIを追加
    DashboardController.php
    public 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]);
    }
    
    
  5. ルート設定
    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を追加

この対応によって、支出一覧と合計金額がリアルタイムで同期されるようになりました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?