はじめに
この記事は、Laravel + Vue.js + Inertia.jsを使って家計簿アプリを作成する過程で学んだことをまとめた備忘録です。
フラッシュメッセージによるトースト通知機能のコンポーネント化を行い、さらに編集機能実施とその通知までの流れをまとめました。
トースト通知をVueコンポーネント化
1.Toast.vue
を作成
非表示コンポーネントとして、以下のように作成します。ページ内におくだけで動作します。
resources/js/Components/Toast.vue
<script setup>
import Swal from 'sweetalert2';
import { watch } from 'vue';
import { usePage } from '@inertiajs/vue3';
const page = usePage();
watch(
() => page.props.flash?.message,
(message) => {
if (message) {
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: message,
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
});
}
}
);
</script>
<template>
<!-- 非表示 -->
</template>
2.Dashboard.vue
に組み込む
<script setup>
import Toast from '@/Components/Toast.vue';
</script>
<template>
<Head title="アカウントトップページ" />
<AuthenticatedLayout>
<Toast /> <!-- 追加 -->
<!-- 既存のコード -->
</AuthenticatedLayout>
</template>
金額が少数で表示される->整数に修正
テーブル設計変更
金額を整数で管理するため、decimal
->integer
へ変更。
###マイグレーション作成
./vendor/bin/sail artisan make:migrate change_amount_to_integer_in_expenses_table --table=expenses
内容を記述
public function up(): void
{
Schema::table('expenses', function (Blueprint $table) {
$table->integer('amount')->change();
});
}
public function down(): void
{
Schema::table('expenses', function (Blueprint $table) {
$table->decimal('amount', 8, 2)->change();
});
}
実行
./vendor/bin/sail artisan migrate
編集機能の実装
1.ルーティング追加
Route::get('/expenses/{expense}/edit', [DashboardController::class, 'edit'])->name('expenses.edit');
Route::get('/expenses/{expense}', [DashboardController::class, 'update'])->name('expenses.update');
2.コントローラ処理
編集ページの表示
public function edit(Expense $expense)
{
if (auth()->id() !== $expense->user_id) {
abort(403);
}
return Inertia::render('Expenses/Edit', [
'expense' => $expense,
]);
}
更新処理
public function update(Request $request, Expense $expense)
{
if (auth()->id() !== $expense->user_id) {
abort(403);
}
$validated = $request->validate([
'amount' => 'required|numeric',
'date' => 'required|date',
'title' => 'required|string|max:255',
'category' => 'required|string|max:255',
]);
$expense->update($validated);
return redirect()->route('dashboard')->with('message', '更新しました');
}
3.一覧画面に「編集」追加
<td class="border px-4 py-2">
<Link :href="route('expenses.edit', expense.id)" class="text-blue-500 hover:underline">編集</Link>
<DeleteButton :expenseId="expense.id" />
</td>
4.edit.vue
の新規作成
resources/js/Pages/Expense/Edit.vue
<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Head, useForm } from '@inertiajs/vue3';
import InputLabel from '@/Components/InputLabel.vue';
import TextInput from '@/Components/TextInput.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import InputError from '@/Components/InputError.vue';
const props = defineProps({ expense: Object });
const form = useForm({
amount: props.expense.amount,
date: props.expense.date,
title: props.expense.title,
category: props.expense.category,
});
const submit = () => {
form.put(route('expenses.update', props.expense.id), {
onSuccess: () => form.reset(),
});
};
</script>
<template>
<Head title="編集ページ" />
<AuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold">編集ページ</h2>
</template>
<form @submit.prevent="submit" class="p-6 space-y-4">
<div>
<InputLabel for="amount" value="金額" />
<TextInput id="amount" type="number" v-model="form.amount" />
<InputError :message="form.errors.amount" />
</div>
<div>
<InputLabel for="date" value="日付" />
<TextInput id="date" type="date" v-model="form.date" />
<InputError :message="form.errors.date" />
</div>
<div>
<InputLabel for="title" value="費用名" />
<TextInput id="title" type="text" v-model="form.title" />
<InputError :message="form.errors.title" />
</div>
<div>
<InputLabel for="category" value="カテゴリー" />
<TextInput id="category" type="text" v-model="form.category" />
<InputError :message="form.errors.category" />
</div>
<PrimaryButton class="mt-4">更新</PrimaryButton>
</form>
</AuthenticatedLayout>
</template>
編集後にトースト通知が出ない?
原因
Toast.vue
ではwatch
を使っていたため、リダイレクト後のページ読み込みでは検知されない。
改善案:onMounted
を使う
Dashboard.vue
に以下を追加。
import { onMounted } from 'vue';
onMounted(() => {
if (page.props.flash?.message) {
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: page.props.flash.message,
showConfirmButton: false,
timer: 2000,
timerProgressBar: true,
});
}
});
詳細:watch
とonMounted
の違い
比較 | 特徴 |
---|---|
watch |
Vueのリアクティブ変化時に反応(フォーム送信など) |
onMounted |
ページ読み込み時に実行(リダイレクト直後など) |
今回の対応
- 新規登録 ->
watch
が有効(SPA内部処理) - 編集や削除 ->
onMounted
が必要(リダイレクトあり)
まとめ
- トースト通知はSweetAlert2 + Vueの非表示コンポーネントで実装
- 編集機能では
form.put()
によるリダイレクト後の通知に注意 -
watch
だけでなくonMounted
も併用して対応
Inertia.jsでSPA風に構築していると、Vueのライフサイクルや通知の挙動に気づきづらい点も多いため、しっかり整理して対応していく。