0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue.js(Inertia.js)×Laravelでトースト通知をコンポーネント化して編集機能に対応するまでの備忘録

Posted at

はじめに

この記事は、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,
        });
    }
});

詳細:watchonMountedの違い

比較 特徴
watch Vueのリアクティブ変化時に反応(フォーム送信など)
onMounted ページ読み込み時に実行(リダイレクト直後など)

今回の対応

  • 新規登録 -> watchが有効(SPA内部処理)
  • 編集や削除 -> onMountedが必要(リダイレクトあり)

まとめ

  • トースト通知はSweetAlert2 + Vueの非表示コンポーネントで実装
  • 編集機能ではform.put()によるリダイレクト後の通知に注意
  • watchだけでなくonMountedも併用して対応

Inertia.jsでSPA風に構築していると、Vueのライフサイクルや通知の挙動に気づきづらい点も多いため、しっかり整理して対応していく。

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?