Inertiaで共通データとフラッシュメッセージ Inertia.js入門 #9

Inertia.js入門 共通データとフラッシュメッセージ Laravel

この記事では、Inertiaのレスポンスに毎回同じデータを含める方法やフラッシュメッセージの使い方について解説します。

前回の記事ではInertia.jsでのバリデーションについて解説しました。このシリーズ記事ではInertia.jsの使い方を解説していますので以前の記事と併せてお読みください。

Laraveler
Laraveler

アプリ全体で使用する設定値やフラッシュメッセージをInertiaレスポンスに含めたいんですが、毎回コントローラーで渡すのは無駄が多い気がするんです。

シップ
シップ

LaravelのミドルウェアでInertiaレスポンスに毎回含めたいデータを指定することができますよ。

アプリ全体で使用する設定値を毎回のレスポンスに含める

バックエンドのLaraveで設定した値を、フロントエンドのVueで使いたい場合、毎回コントローラーで値を渡すのは効率が悪いため、ミドルウェアで一律で設定するとすべてのレスポンスに含まれるようになります。

ここでは、例としてこのシリーズ記事で使用しているサンプルのブックマーク数の最大値を、.envファイルで設定し、それをVue側で使えるようにするという設定を行います。

.envファイルで設定値を設定

//.envファイルに追記
MAX_BOOKMARKS=20

configとして扱えるようにする

「config/app.php」に次の行を追記します。

return [
    //略
    'max_bookmarks' => env('MAX_BOOKMARKS'),
];

これで、Laravelの中で次のコードで.envで設定した「MAX_BOOKMARKS」の値を使えるようになります。

config('app.max_bookmarks')

env('MAX_BOOKMARKS')を直接使うことは避けましょう。理由としては、本番環境では設定ファイルがキャッシュされ、その場合env()関数はnullを返すようになるためです。

Inertiaのミドルウェアで設定値を返す

「app/Http/Middleware/HandleInertiaRequests.php」がInertiaのミドルウェアファイルです。毎回のリクエスト時にこの内容が読み込まれます。

今回はその中のshare()メソッドの返り値にsetting配列を追加し、max_bookmarksキーでブックマーク数の最大値をセットします。

    //(略)  
    public function share(Request $request)
    {
        return array_merge(parent::share($request), [
            'setting' => [
                'max_bookmarks' => config('app.max_bookmarks'),
            ]
        ]);
    }
    //(略)

この設定値はInertiaレスポンスの中でこのような形で含まれるようになります。

"props": {
    "setting":{
        "max_bookmarks":"20"
    }
}

Vueコンポーネントでの設定値の使用

propsに渡された値はVueコンポーネントからは次のように参照できます。

import { usePage } from '@inertiajs/vue3'

const page = usePage()
console.log(page.props.setting.max_bookmarks)

渡された設定値を利用してブックマークの登録数をプログレスバーで表示するサンプルです。

        <div class="flex w-full">
            <progress v-if="page.props.setting.max_bookmarks" :value="bookmarks.length" :max="page.props.setting.max_bookmarks" class="w-full flex-grow">{{ bookmarks.length }}</progress>
            <div class="w-20">{{ bookmarks.length }} / {{ page.props.setting.max_bookmarks }}</div>
        </div>

ミドルウェアで渡す値も、コントローラーで渡す値も同じpropsのデータとして混合してから渡されるため、名前が衝突しないように注意する必要があります。

フラッシュメッセージを利用する

フラッシュメッセージとは、一回のみ表示されるメッセージのことで、何かのアクションに対する応答を表示する場合などに用います。

コントローラーでフラッシュメッセージを設定

フラッシュメッセージの設定方法はいくつかありますが、ここではwith()メソッドで追加する方法を用います。

    //コントローラーの削除メソッド
    public function destroy ($id) 
    {
        Bookmark::destroy($id);
        return to_route('bookmark.index')->with('message', '削除しました'); //削除後ブックマーク一覧ページへリダイレクト
    }

ブックマーク削除後のレスポンスにwith()メソッドをチェインしています。
これでセッションのmessageに「削除しました」というメッセージが入ります。

ミドルウェアでレスポンスにフラッシュメッセージを含める

先ほど設定値をミドルウェアで返すようにした際と同様に「app/Http/Middleware/HandleInertiaRequests.php」のshare()メソッドの返り値にflash配列を追加します。

flash配列にはmessageキーにフラッシュメッセージの内容をセッションから取り出してセットしています。

    //(略)
    public function share(Request $request)
    {
        return array_merge(parent::share($request), [
            //(略)
            'flash' => [
                'message' => fn () => $request->session()->get('message')
            ],
        ]);
    }
    //(略)

このフラッシュメッセージは次のようなJSONデータとして渡されます。

"props": {
	"flash": {
		"message": "削除しました"
	}
}

Vueコンポーネントでのフラッシュメッセージの使用

propsにflashとして渡された値はVueコンポーネントからは次のように参照できます。

import { usePage } from '@inertiajs/vue3'

const page = usePage()
console.log(page.props.flash.message)

渡されたフラッシュメッセージを親レイアウトで表示するサンプルです。

            <div v-if="page.props.flash.message" class="bg-green-200 p-2 m-1">
                {{ page.props.flash.message }}
            </div>

サンプルソース

今回のサンプルソースです。完全なソースはGitHubをご覧ください。

ミドルウェア

<?php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    /**
     * The root template that's loaded on the first page visit.
     *
     * @see https://inertiajs.com/server-side-setup#root-template
     * @var string
     */
    protected $rootView = 'app';

    /**
     * Determines the current asset version.
     *
     * @see https://inertiajs.com/asset-versioning
     * @param  \Illuminate\Http\Request  $request
     * @return string|null
     */
    public function version(Request $request): ?string
    {
        return parent::version($request);
    }

    /**
     * Defines the props that are shared by default.
     *
     * @see https://inertiajs.com/shared-data
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'setting' => [
                'max_bookmarks' => config('app.max_bookmarks'),
            ],
            'flash' => [
                'message' => fn () => $request->session()->get('message')
            ],
        ]);
    }
}

Vueコンポーネント(ブックマーク上限数の表示)

<script setup>
import { reactive, computed } from "vue";
import { Link, router, useForm, usePage } from '@inertiajs/vue3'
import Layout from '@/Pages/Layout.vue'

const page = usePage()
const props = defineProps({ 
    bookmarks: {
        type: Array
    }, 
    errors: {
        type: Object
    }
});
const form = useForm({
    title: null,
    url: null,
});

const submit = () => {
    form
    .transform((data) => ({
        ...data,
        title: data.title ? data.title : '無題',
    }))
    .post(route('bookmark.store'));
}
</script>

<template>
    <Layout title="ブックマーク一覧">
        <h2 class="text-lg border-b p-2">
            <Link :href="route('bookmark.index')" class="text-blue-700 underline">
            ブックマーク一覧
            </Link>
        </h2>
        <ul class="list-disc list-inside p-2">
            <li v-for="bookmark in bookmarks" class="list-none flex justify-between">
                <a :href="bookmark.url" target=_blank class="text-blue-700" >{{ bookmark.title }}</a>
                <Link as="button" method="delete" :href="route('bookmark.delete',bookmark.id)" preserve-scroll  class="border border-red-400 m-1 p-1 text-sm text-red-400">削除</Link>
            </li>
        </ul>
        <div class="flex w-full">
            <progress v-if="page.props.setting.max_bookmarks" :value="bookmarks.length" :max="page.props.setting.max_bookmarks" class="w-full flex-grow">{{ bookmarks.length }}</progress>
            <div class="w-20">{{ bookmarks.length }} / {{ page.props.setting.max_bookmarks }}</div>
        </div>
        <template v-if="bookmarks.length < page.props.setting.max_bookmarks">
            <form @submit.prevent="submit">
                <h3 class="border-l-4 p-1">ブックマーク追加</h3>
                <div class="flex w-full">
                    <div class="w-20 text-right bg-gray-200 text-sm p-1 m-1">タイトル</div>
                    <div class="w-40">
                        <input type=text v-model="form.title" size=15 class="p-1 m-1 text-sm w-full border" />
                    </div>
                    <div v-if="form.errors.title" class="p-1 m-1 text-sm text-red-400">{{ form.errors.title }}</div>
                </div>
                <div class="flex w-full">
                    <div class="w-20 text-right bg-gray-200 text-sm p-1 m-1">URL</div>
                    <div class="w-40">
                        <input type=text v-model="form.url" size=15 class="p-1 m-1 text-sm w-full border" />
                    </div>
                    <div v-if="form.errors.url" class="p-1 m-1 text-sm text-red-400">{{ form.errors.url }}</div>
                </div>
                <button type="submit" class="border border-gray-400 m-1 p-1 text-sm" :disabled="form.processing" v-bind:class="{'cursor-not-allowed': form.processing}">ブックマーク追加</button>
                <button v-if="form.processing" @click="form.cancel()" type="button" class="border bg-red-200 m-1 p-1 text-sm">送信中止</button>
                <button @click="form.reset()" type="reset" class="border bg-gray-200 m-1 p-1 text-sm" :disabled="!form.isDirty" v-bind:class="{'cursor-not-allowed': !form.isDirty}">リセット</button>
                <button @click="form.defaults()" type="button" class="border bg-gray-200 m-1 p-1 text-sm" :disabled="!form.isDirty" v-bind:class="{'cursor-not-allowed': !form.isDirty}">初期値として設定</button>
                <div v-if="form.recentlySuccessful" class="p-1 m-1 bg-green-200">送信が完了しました。</div>
            </form>
        </template>
        <template v-else>
            ブックマークが上限まで追加されています。
        </template>
    </Layout>
</template>

コントローラー(フラッシュメッセージを作成)

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Bookmark;
use Illuminate\Support\Facades\Validator;

class BookmarkController extends Controller
{
    public function index () 
    {
        return Inertia::render('Bookmark/Index',['bookmarks' => fn() => Bookmark::all()]);
    }
    
    //検索メソッド
    public function search ($queryWord) 
    {
        return Inertia::render('Bookmark/Index',[
            'bookmarks' => Bookmark::Where(
                                'title', 'like', '%'.$queryWord.'%'
                            )->orWhere(
                                'url', 'like', '%'.$queryWord.'%'
                            )->get()
        ]);
    }

    //追加メソッド
    public function store (Request $request) 
    {
        $validator = Validator::make($request->all(), [
            'title' => ['required', 'string', 'max:255'],
            'url' => ['required', 'url', 'max:255', 'unique:App\Models\Bookmark,url'],
        ])->validate();

        $bookmark = new Bookmark;
        $bookmark->title = $request->title;
        $bookmark->url = $request->url;
        $bookmark->save();

        return to_route('bookmark.index')->with('message', '追加しました'); //追加後ブックマーク一覧ページへリダイレクト
    }

    //削除メソッド
    public function destroy ($id) 
    {
        Bookmark::destroy($id);
        return to_route('bookmark.index')->with('message', '削除しました'); //削除後ブックマーク一覧ページへリダイレクト
    }
}

親レイアウトVueコンポーネント(フラッシュメッセージ表示)

<script setup>
import { ref } from "vue";
import { Link, Head, usePage } from '@inertiajs/vue3'

const page = usePage()
defineProps({ title: String })
const searchWord = ref('');
</script>

<template>
    <Head :title="title" />
    <main>
        <header class="bg-gray-100">
            <Link href="/" class="text-blue-700 underline m-2">Home</Link>
            <Link href="/HelloWorld" class="text-blue-700 underline m-2">HelloWorld</Link>
            <Link :href="route('bookmark.index')" class="text-blue-700 underline m-2">ブックマーク一覧</Link>
            <input type=text name=search v-model="searchWord" size=15 class="p-1 m-1 text-sm" />
            <Link as="button" method="get" :href="route('bookmark.search',{queryWord: searchWord})" preserve-state class="border border-gray-400 m-1 p-1 text-sm">ブックマーク検索</Link>
        </header>
        <article class="max-w-screen-sm">
            <div v-if="page.props.flash.message" class="bg-green-200 p-2 m-1">
                {{ page.props.flash.message }}
            </div>
            <slot />
        </article>
    </main>
</template>
表示例
今回のまとめ
  • アプリ全体の共通データをInertiaのミドルウェアで返すことで全てのレスポンスに共有データとして含めることができる。
  • フラッシュメッセージも同様にコントローラーとミドルウェアでレスポンスにセットすることでVue側で使用できる
  • 共有データやフラッシュメッセージはpropsから参照できる。

全9回にわたるInertia.js入門もこの記事が最後となりました。必要に応じて過去の記事もご参照ください。

Inertia.js入門記事一覧
Inertia.jsでシンプルにSPAを構築する Inertia入門#1
Inertia.jsを使うとLaravelやRubyonRailsなどのフレームワーク上でAjax用のAPIやコントローラーを作成しなくても、通常のビューを使う要領でSPA(シングルページアプリケーション)が構築できます。
Laravel 10+Inertia.js+Tailwindインストール Inertia入門#2
この記事ではLaravelにInertia.jsをインストールしてHelloWorldを出力する方法までを解説します。
Inertia.jsのルーティングとレスポンス作成 Inertia入門#3
Laravel8でjetstreamを入れるとLimewireかInertiaかを選べます。この記事ではInertiaを使ってルーティングやInertiaレスポンスの返し方を解説します。
Inertia.jsでページレイアウト Inertia入門#4
この記事では、ページレイアウトについて解説します。親レイアウトに子ページを組み込むことで、ヘッダーやフッターを共通化できます。
Inertia Linkの使い方 Inertia.js入門 #5
この記事では、Inertia.jsのLinkコンポーネントについて解説します。
Inertia.jsでXHRリクエスト Inertia.js入門 #6
この記事では、Inertia.jsで非同期通信を行うrouter.visit()について解説します。
Inertia.jsでのフォームとリダイレクト Inertia.js入門 #7
この記事では、Inertia.jsでのフォーム送信とリダイレクトについて解説します。
Inertiaでのバリデーション Inertia.js入門 #8
この記事では、Inertia.jsを使った際のバリデーションエラーの表示について解説します。
Inertiaで共通データとフラッシュメッセージ Inertia.js入門 #9
この記事では、Inertiaのレスポンスに毎回同じデータを含める方法やフラッシュメッセージの使い方について解説します。

この記事を書いた人

PHPが好物な個人開発プログラマ。フリーランスエンジニアとしてWebサービス作ったりしてます。15年の経験を生かしてMENTAでメンターもやってます。WordPressやPHPでお困りのことがあればご相談に乗りますのでDMください。

Follow on SNS
Laravel
SOHO MIND

Comments

タイトルとURLをコピーしました