Laravel Jetstream入門 Inertia.js編

Laravel8ではJetstreamをインストールする際にInertiaかlivewireを選択する必要があります。Inertiaを選択したけどインストールした後に何をしていいのかわからないまたIneriatがLaravel8インストール後にどの部分で利用されているのか知りたいという人も多いかと思います。本文書ではJetstreamと一緒にインストールされるInertia.jsがLaravel8でどのような処理に関わっているかの説明をコードを利用して行っています。
Inertia.jsで作成されたページのコードを理解するためにはprops, slot,ディレクティブ, イベントなどのVue.jsの基礎知識が必要となります。本文書ではVue.jsの知識があることを前提に説明を行っています。
Inetria.jsではなくLivewireを使ってアプリケーションを構築するのでLivewireについて知りたい方は下記を参考にしてください。
Laravel環境の構築
動作確認を行うLaravelをMacを利用して構築しています。
Laravelをインストール時に一緒にInertia.jsをインストールを行いたい場合はlaravel newコマンドに–jetオプションを付与して実行します。ここではLaravelのプロジェクト名をlaravel8_inertiaにしていますが、任意の名前をつけてください。実行後inertiaかlivewireの選択が出てくるのでinetiaを選択してください。teamのインストールも確認されますが、どちらを選択しても構いません。
% laravel new laravel8_inertia --jet
Laravelインストール後にJetstreamをインストールすることも可能です(–jetのオプションをつけずにインストールした場合)。その場合はパッケージをインストールして、php artisanにinertiaを指定します。
% composer require laravel/jetstream
% php artisan jetstream:install inertia
jetstreamインストール後にnpm install && npm run devを実行するようにメッセージが表示されるので実行してください。
% npm install && npm run dev

どちらの場合もインストール後にphp artisan serveコマンドで開発サーバを起動してブラウザでアクセスすると下記のConnection Refusedエラーが表示されます。設定不備により接続が拒否されたわけではなくデータベースが未作成のため接続できないことがエラーの原因です。JetstreamのInertia.jsを利用するためにはデータベースの作成が必須となります。

本文書では簡易的なsqliteデータベースを利用します。touchコマンドでsqliteのデータベースファイルを作成して.envファイルのDBに関する環境変数を変更します。
% touch database/database.sqlite
.envファイルではDB_CONNECTIONをsqliteに変更し、その他のDBに関連する環境変数を削除します。削除する環境変数うは先頭にDB_がついているものです。
DB_CONNECTION=sqlite
これでデータベースの設定が完了できたので、php artisan migrateコマンドを実行してください。エラーなくテーブルが作成されるはずです。
% php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_000000_create_users_table (3.81ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated: 2014_10_12_100000_create_password_resets_table (2.52ms)
Migrating: 2014_10_12_200000_add_two_factor_columns_to_users_table
Migrated: 2014_10_12_200000_add_two_factor_columns_to_users_table (2.63ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated: 2019_08_19_000000_create_failed_jobs_table (1.68ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated: 2019_12_14_000001_create_personal_access_tokens_table (2.33ms)
Migrating: 2020_10_22_092603_create_sessions_table
Migrated: 2020_10_22_092603_create_sessions_table (2.01ms
php artisan serveコマンドで開発サーバを起動し、アクセスを行うと初期画面が表示されます。

これでInertia.jsで動作確認するためのLaravel環境の構築は完了です。
Inertia.jsで作成されたページを表示
Inertia.jsを選択した場合、Inertia.js(+vue.js)を利用してユーザ登録画面、ログイン画面なども作成していると思っている人もいるかもしれませんが、それらの認証画面についてはこれまで通りBladeを利用して作成されています。そのためInertia.jsでもLivewireでも認証画面に関しては共通のファイルを利用しています。
Jetstreamの認証については下記を参考にしてください。
Inertia.jsを利用して作成されているページにアクセスするためにまずユーザ登録画面からユーザを作成してをログインを行ってください。
ユーザ登録は右上のリンクから行うことができます。

ユーザ登録を行うと登録と同時にログイン処理が行われ、ダッシュボードにリダイレクトされます。このログイン後最初に表示されるダッシュボードがInertia.jsを利用して作成されています。
ルーティングのweb.phpファイルを開くとInertia.jsが利用されていることがわかります。render関数で指定しているファイルの内容が表示されます。
Route::middleware(['auth:sanctum', 'verified'])->get('/dashboard', function () {
return Inertia\Inertia::render('Dashboard');
})->name('dashboard');
Bladeファイルであればファイルの保存先はresources¥viewsの下ですがInertia.jsを利用した場合はresources¥jsの下に保存され、ファイルの拡張子は.vueになります。拡張子を確認することで改めてvue.jsを利用していることがわかります。
resouces¥js¥Pagesの下にあるDashboard.vueを開いてみましょう。vueファイルなのでtemplateとscriptタグで構成されています。CSSについてはtailwindcssを利用していためstyleタグは利用していません。
Dashboard.vueではimportを利用してDashboard.vueからさらに別のコンポーネントをインポートしているのがわかります。vue.jsに慣れている人にとっては見慣れたコードだと思います。
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Dashboard
</h2>
</template>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
<welcome />
</div>
</div>
</div>
</app-layout>
</template>
<script>
import AppLayout from '@/Layouts/AppLayout'
import Welcome from '@/Jetstream/Welcome'
export default {
components: {
AppLayout,
Welcome,
},
}
</script>
Dashboardの文字列にInertia.jsを追加することでブラウザに表示されているダッシュボードの画面に反映されるか確認します。
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Dashboard Inertia.js
</h2>
しかしリロードしても変更は反映されていません。Bladeファイルではなくvueファイルなのでビルドが必要となるります。vueファイルを更新して反映させるためには、npm run watchコマンドを実行しておく必要があります。
% npm run watch
ビルドが完了すると更新が反映されます。

AppLayout.vueファイルの更新
Dashboard.vueでimportしているAppLayout.vueがページ全体のデザインを構成するコンポーネントなのでこのファイルを更新することで全体のイメージを変更することができます。
CSSはtailwindcssを利用しているので背景色を変更したい場合は背景色を設定しているclassのbg-gray-100を別のclassに変えることで変更が可能です。例えばbg-red-500に変更すると背景色が赤になります。
<template>
<div class="min-h-screen bg-gray-100">
<nav class="bg-white border-b border-gray-100">
<!-- Primary Navigation Menu -->
//略
ブラウザで確認すると以下のように変更が確認できます。AppLayout.vueページ全体のデザイン変更を行うことがわかりました。

ここまでの確認では通常のVue.jsとの違いがわかりません。JetStreamの機能の一つであるプロファイル画面の更新を通してInertia.jsがどのように利用されているのか確認していきましょう。
プロファイル画面
プロファイル画面へは右上の名前をクリックして表示されるドロップダウンメニューから移動することができます。

Profileをクリックするとページのフルリロードを行うことなくDashboardからProfileのページを移動することができます。


URLが/dashboardから/user/profileに変わりましたが、ルーティングweb.phpファイルには/user/profileのルーティングが登録されていません。
/user/profileのルーティングの登録を確認するためにはJetStreamのサービルプロバイダーファイルであるJetStreamServiceProvider.phpファイルを確認する必要があります。
JetStreamServiceProvider.phpファイルのbootメソッドのconfigureRoutesメソッドでルーティングの登録が行われています。
protected function configureRoutes()
{
if (Jetstream::$registersRoutes) {
Route::group([
'namespace' => 'Laravel\Jetstream\Http\Controllers',
'domain' => config('jetstream.domain', null),
'prefix' => config('jetstream.path', null),
], function () {
$this->loadRoutesFrom(__DIR__.'/../routes/'.config('jetstream.stack').'.php');
});
}
}
loadRoutesFromメソッドのconfigに指定されているjetstream.stackはconfigディレクトリのJetstreamの設定ファイルであるjetstream.phpから取得されます。stackの値はinertiaに設定されているのでルーティング情報はroutes/inertia.phpファイルから読み込まれていることがわかります。
ルーティングに関する情報を持つinertia.phpファイルはvendor¥laravel¥jetstream¥routesの下に保存されています。
inertia.phpファイルの中で/user/profileのルーティングを確認することができます。Jetstreamで利用されているルーティングもInertia.jsで確認することができます。
Route::group(['middleware' => config('jetstream.middleware', ['web'])], function () {
Route::group(['middleware' => ['auth', 'verified']], function () {
// User & Profile...
Route::get('/user/profile', [UserProfileController::class, 'show'])
->name('profile.show');
//略
/user/profileにアクセスした際に実行されるControllerファイルはUserProfileController.phpのshowメソッドでvendor¥laravel¥jetstream¥src¥Http¥Controllers¥Inertiaの下に保存されています。
UserProfileController.phpのshowメソッドを確認するとrenderメソッドが実行されています。
class UserProfileController extends Controller
{
public function show(Request $request)
{
return Jetstream::inertia()->render($request, 'Profile/Show', [
'sessions' => $this->sessions($request)->all(),
]);
}
//略
renderメソッドの中身から/user/profileにアクセスするとresouces¥js¥Pages¥ProfileのShow.vueファイルであることがわかります。bladeファイルではviewメソッドを利用してBladeファイルを指定していましたが、Inertia.jsではrenderメソッドを利用してvueファイルを指定します。
Profile Informationの確認
Show.vueファイルを使って表示される画面の中でInertia.jsの動作確認を行うために赤線で囲まれた上部のProfile Informationに注目します。Profile Infomationではユーザ名とメールアドレスを変更することができます。

Show.vueのコードのupdate-profile-information-formタグがProfile Informationの表示部分に対応します。タグで表示される内容はUpdateProfileInformationForm.phpファイルが対応しscriptタグの中でimportされていることでわかります。
update-profile-information-formタグにはpropsのuserで$page.userが渡されています。$pageはInertia.jsに関係しているため$pageがどこで設定されているのか確認する必要があります。
<template>
<app-layout>
<template #header>
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
Profile
</h2>
</template>
<div>
<div class="max-w-7xl mx-auto py-10 sm:px-6 lg:px-8">
<update-profile-information-form :user="$page.user" />
<jet-section-border />
//略
変数$pageとは
先ほどAppLayout.vueを確認した時に触れませんでしたが、AppLayout.vueファイルの中でも$pageが使われています。$pageが各ページで利用されていることを考えるとなにかデータ共有の仕組みが備わっていることが予想できます。
Laravelの公式ドキュメント中ではInertia.jsの共有に関する情報を見つけることはできませんでしたが、InertiaのドキュメントからShared dataとしてすべてのページでデータ共有できる仕組みがある事がわかります。

Inertia.jsではデータの共有方法は下記のようにshareメソッドを使って行います。共有したデータについては$pageを使ってアクセスすることができます。
Inertia::share('appName', config('app.name'));
どこでデータの共有処理を行っているのか見つけないといけませんが全ページで共有ということなのでサービスプロバイダーで何か登録を行っていないか確認してみましょう。

サービスプロバイダーファイルのJetstreamServiceProvider.phpのbootメソッドの中でjetstream.stackがinertiaの場合のみbootInertiaが実行されていることが確認できます。
public function boot()
{
$this->loadViewsFrom(__DIR__.'/../resources/views', 'jetstream');
//略
if (config('jetstream.stack') === 'inertia') {
$this->bootInertia();
}
}
bootInertiaメソッドの中でmiddlewareにShareInertiaDataをappendしているメソッドを見つけることができます。名前からもデータ共有の処理に関連していることがわかります。
protected function bootInertia()
{
$kernel = $this->app->make(Kernel::class);
$kernel->appendMiddlewareToGroup('web', ShareInertiaData::class);
$kernel->appendToMiddlewarePriority(ShareInertiaData::class);
}

ShareInertiaDataはmiddlewareなのでhandleメソッドの中で処理が行われ、Inertia::shareを見つけることができます。
class ShareInertiaData
{
public function handle($request, $next)
{
Inertia::share(array_filter([
'jetstream' => function () use ($request) {
return [
//略
];
},
'user' => function () use ($request) {
if (! $request->user()) {
return;
}
if (Jetstream::hasTeamFeatures() && $request->user()) {
$request->user()->currentTeam;
}
return array_merge($request->user()->toArray(), array_filter([
'all_teams' => Jetstream::hasTeamFeatures() ? $request->user()->allTeams() : null,
]), [
'two_factor_enabled' => ! is_null($request->user()->two_factor_secret),
]);
},
'errorBags' => function () {
return
//略
]));
return $next($request);
}
}
共有するデータとして、jetstream, user, errorbagsの3つに分類されていますが、userについては下記のユーザ情報の配列を設定しています。
array:10 [▼
"id" => 1
"name" => "John Doe"
"email" => "john@example.com"
"email_verified_at" => null
"current_team_id" => null
"profile_photo_path" => null
"created_at" => "2020-10-22T10:06:29.000000Z"
"updated_at" => "2020-10-22T10:06:29.000000Z"
"profile_photo_url" => "https://ui-avatars.com/api/?name=John+Doe&color=7F9CF5&background=EBF4FF"
"two_factor_enabled" => false
]
jetstreamで共有される内容は下記となります。jetstream.php、fortify.phpファイルでJestreamのサービスを有効・無効を設定することができます。この設定値にアクセスすることでサービスに関連する情報を表示するかさせないかの分岐を行っています。
array:6 [▼
"canCreateTeams" => false
"canManageTwoFactorAuthentication" => true
"flash" => []
"hasApiFeatures" => false
"hasTeamFeatures" => false
"managesProfilePhotos" => false
]
データの共有処理が行われることで$page.userで上記のuserの値にアクセスすることができます。
UpdateProfileInformationFormコンポーネントに渡していたpropsのuserの中身が特定できたのでUpdateProfileInformationFormコンポーネントを確認します。
UpdateProfileInformationFormコンポーネント
UpdateProfileInformationFormを開くとその中のコンテンツは<jet-form-section>タグにwrapされていることがわかります。
<template>
<jet-form-section @submitted="updateProfileInformation">
<template #title>
Profile Information
</template>
//略
jet-form-sectionタグはimportからJetFormSectionコンポーネントに対応していることがわかります。resources¥js¥Jetstreamの下にあるFormSection.vueファイルを確認します。

FormSection.vueの中身は4つの名前付きSlot(title, description, form, actions)が設定されていおり、UpdateProfileInformationForm.vueのjet-form-sectionタグ内のコンテンツが各名前付き Slotの中に挿入されます。
例えば下記のUpdateProfileINformationForm.vueのtitleとdescriptionは、FromSection.vueの名前付きSlotに挿入されます。
<template #title>
Profile Information
</template>
<template #description>
Update your account's profile information and email address.
</template>
さらにFormSection.vueではSlotで受け取ったtitleととdescriptionをSectionTitle.vueにSlotで渡しています。
<jet-section-title>
<template #title><slot name="title"></slot></template>
<template #description><slot name="description"></slot></template>
コンポーネントの関係は単純な2層ではなく孫コンポーネントをもつような多層構造をしています。
FormSectionコンポーネント
FormSection.vueの中で注目するのはsubmitイベントで、$emitを使って親コンポーネントにsubmittedイベントを渡しています。
<form @submit.prevent="$emit('submitted')">
form内のボタンがクリックされると親コンポーネントにsubmittedイベントが伝えられ、親コンポーネントがそのイベントを受け取って処理を行います。
親コンポーネントのUpdateProfileINformationFormを確認するとsubmittedイベントを受け取りupdateProfileInformationメソッドが実行されることがわかります。
<jet-form-section @submitted="updateProfileInformation">
updateProfileInformationメソッドはscriptタグの中に記述されています。
data() {
return {
form: this.$inertia.form({
'_method': 'PUT',
name: this.user.name,
email: this.user.email,
photo: null,
}, {
bag: 'updateProfileInformation',
resetOnSuccess: false,
}),
photoPreview: null,
}
},
methods: {
updateProfileInformation() {
if (this.$refs.photo) {
this.form.photo = this.$refs.photo.files[0]
}
this.form.post(route('user-profile-information.update'), {
preserveScroll: true
});
},
データプロパティのformの構造などわからない点はありますが、formプロパティのformメソッドの中にmethodでPUTが指定されていること、updateProfileInformationメソッドのpostにrouteが設定されていることでボタンを押すとsubmittedイベントが発行され、そのイベント後にPOSTリクエストがuser-profile-information.updateに送信されていることはわかります。
user-profile-information.updateについて
routeメソッドに設定されているuser-profile-information.updateがどこ場所なのかルーティングを確認する必要があります。
user-profile-information.updateはFortilyServiceProvider.phpファイルで読み込まれるvendor¥laravel¥fortify¥rotes¥routes.phpファイルに記述されています。
// Profile Information...
if (Features::enabled(Features::updateProfileInformation())) {
Route::put('/user/profile-information', [ProfileInformationController::class, 'update'])
->middleware(['auth'])
->name('user-profile-information.update');
}
実行される処理はProfileInformationControllerのupdateメソッドであることがわかります。
ProfileInformationControllerのupdateメソッドの中では、$updaterのupdateメソッドが実行されていることはわかりますが、その処理を理解するためにはDependency InjectionのUpdateUserProfileInformationが何かを確認する必要があります。
UpdateUserProfileInformationはインターフェイスなのでサービスコンテナに対応するクラスが登録されているはずなのでそれを探す必要があります。
class ProfileInformationController extends Controller
{
public function update(Request $request,
UpdatesUserProfileInformation $updater)
{
$updater->update($request->user(), $request->all());
return $request->wantsJson()
? new JsonResponse('', 200)
: back()->with('status', 'profile-information-updated');
}
}
まずapp¥Providers¥FortifyServiceProvider.phpファイルのbootメソッドでUpdateUserProfileInformationを見つけることができます。
public function boot()
{
Fortify::createUsersUsing(CreateNewUser::class);
Fortify::updateUserProfileInformationUsing(UpdateUserProfileInformation::class);
Fortify::updateUserPasswordsUsing(UpdateUserPassword::class);
Fortify::resetUserPasswordsUsing(ResetUserPassword::class);
}

Laravel¥Fortify¥Fortify.phpファイルのstaticメソッドのupdateUserProfileInformationUsingを確認するとsingletonでApp¥Actions¥Fortify¥UpdateUserProfileInformationクラスがUpdatesUserProfileInformationとして登録されていることがわかります。
public static function updateUserProfileInformationUsing(string $callback)
{
return app()->singleton(UpdatesUserProfileInformation::class, $callback);
}
ProfileInformationController.phpファイルのupdateメソッドの$updaterの実体がApp¥Actions¥Fortify¥UpdateUserProfileInformationであることがわかりました。
updateメソッドではPOSTリクエストで受け取った値を渡すことでバリデーションが行われ、バリデーション後に$user情報を更新しています。
class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{
public function update($user, array $input)
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
'photo' => ['nullable', 'image', 'max:1024'],
])->validateWithBag('updateProfileInformation');
if (isset($input['photo'])) {
$user->updateProfilePhoto($input['photo']);
}
if ($input['email'] !== $user->email &&
$user instanceof MustVerifyEmail) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
])->save();
}
}
Email Verification(メール検証)の機能を利用している場合は$userがMustVerifyEmailのインスタンスかチェックを行い、MustVerifyEmailのインスタンスの場合はメール検証を行う処理が実行されます。
このバリデーション内容については自由に更新することが可能です。必要であれば更新を行ってください。
user-profile-information.updateにPUTリクエストが送信されてからユーザ情報が更新されるまでの流れがわかりました。
Form / Validationヘルパー
Profile Informationの更新処理の中でまだ説明を行っていない箇所が下記のデータの更新に関わる処理です。どのようにuser-profile-information.updateに対してデータを送信するのかが記述されており、Inertia.jsを利用する中で最も重要な部分です。この部分が理解できないとデータの追加、更新、削除処理を行うことができません。
data() {
return {
form: this.$inertia.form({
'_method': 'PUT',
name: this.user.name,
email: this.user.email,
photo: null,
}, {
bag: 'updateProfileInformation',
resetOnSuccess: false,
}),
photoPreview: null,
}
},
methods: {
updateProfileInformation() {
if (this.$refs.photo) {
this.form.photo = this.$refs.photo.files[0]
}
this.form.post(route('user-profile-information.update'), {
preserveScroll: true
});
},
データプロパティのformを理解することがポイントで、formについてはLaravel Jetsteamの公式ドキュメントに解説が記載されています。理解するためにはドキュメントを確認しておく必要があります。

解説によるとフォーム処理とバリデーション結果のエラー表示を効率的に行うためにlaravel-jetsteamというパッケージがJetstreamインストール時に一緒にインストールされています。追加パッケージがインストールされていることでデータプロパティのformの中の処理についてはInertia.jsの単独の機能ではなくLaravel Jetstream用に機能追加が行われています。
$inertiaオブジェクトでformメソッドを追加し、引数にdata、optionsを設定することで新たにinertiaFormオブジェクトを作成します。
this.$inertia.form(data, options) → intriaFormオブジェクト
dataについて
dataではHTTPメソッドと送信するデータの設定を行うことができます。ここではHTTPメソッドにPUTメソッドを設定しています。
form: this.$inertia.form({
'_method': 'PUT',
name: this.user.name,
email: this.user.email,
photo: null,
}, options),
optionsについて
optionsでは、bag、resetOnSuccessが設定されています。
form: this.$inertia.form(data, {
bag: 'updateProfileInformation',
resetOnSuccess: false,
}),
bagプロパティでupdateProfileInformationを指定していますが、これは更新処理を行うapp¥Actions¥Fortify¥UpdateUserProfileInformation.phpのvalidateWithBagで指定したupdateProfileInformationと一致させています。bagとvalidataWithBagの値を一致させることでerrorBag(バリデーションエラーで発生したエラーが保存されるオブジェクト)からエラー情報を取得する際にvalidateWithBagで指定したupdateProfileInformationを明記しなくてもform.error(‘email’)のような短いコードでエラー情報を取得することができます。
resetOnSuccessはfalseに設定することでputリクエストで更新が完了した後に更新後の値がinput要素に入ります。resetOnsuccessをtrue(デフォルト)に設定した場合は更新が完了した後に更新する前の値がinput要素に入ります。

postメソッド
dataとoptionsを利用して作成したInertiaFormオブジェクトでpostメソッドを実行します。postメソッドではURLとoptionsを設定することができます。
this.form.post(URL, options)
URLには更新処理を行うURLを設定し、optionsにはInertis.jsのオプションを設定します。
this.form.post(route('user-profile-information.update'), {
preserveScroll: true
});

Inertis.jsのオプションであるpreservScrollをtrueにすることでブラウザをスクロールをしてフォームを表示している場合に更新後もスクロールした位置でフォームを表示させることができます。preserveScrollを設定しない場合は、更新後にページの上部から表示されるため入力時に見ていた画面のスクロール位置とは異なる場所が表示されます。
ここまでの説明でどのようにuser-profile-information.updateに対してデータを送信するのかを理解することができました。次はリクエスト送信後に戻ってきた情報をtemplateタグの中でどのように利用しているかを確認していきます。
templateタグの中身
InertiaFormオブジェクトには内部にprocessing, successful, recentlySuccessfulといった変数を持っています。これらの値を利用しながらUIに変化を加えています。
どのように利用しているかを確認していくために入力フォームに関するタグを見ていきます。
<!-- Name -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="name" value="Name" />
<jet-input id="name" type="text" class="mt-1 block w-full" v-model="form.name" autocomplete="name" />
<jet-input-error :message="form.error('name')" class="mt-2" />
</div>
<!-- Email -->
<div class="col-span-6 sm:col-span-4">
<jet-label for="email" value="Email" />
<jet-input id="email" type="email" class="mt-1 block w-full" v-model="form.email" />
<jet-input-error :message="form.error('email')" class="mt-2" />
</div>
</template>
<template #actions>
<jet-action-message :on="form.recentlySuccessful" class="mr-3">
Saved.
</jet-action-message>
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Save
</jet-button>
</template>
v-modelディレクティブの設定
NameとEmailのinput要素ではvue.jsのv-modelディレクティブを利用して入力値とformのnameとemailをバインディングしています。
エラー設定
エラーについては先ほど説明した通り、InertiaFormの設定のおかげでform.error(‘name’)といったシンプルなコードで取得することが可能で<jet-input-error>タグにpropsのmessageを使ってエラーの内容を渡しています。
<jet-input-error>タグに対応するinputError.vueでは受け取ったmessageに値が入っているかどうかをv-showでチェックして表示・非表示を制御しています。
<template>
<div v-show="message">
<p class="text-sm text-red-600">
{{ message }}
</p>
</div>
</template>
<script>
export default {
props: ['message']
}
</script>
エラーが発生した場合は下記のようにinput要素の下にバリデーションのエラーメッセージが表示されます。
Nameに何もいれなかったのでvalidation.requiredが表示されます。バリデーションの変更はApp¥Actions¥Fortify¥UpdateUserProfileInformationで可能です。

InertiaFormの内部変数
inertiaFormオブジェクトのrecentrlySuccessfullとprocessingが利用されているslotのactionの中身を詳しくみていきましょう。
<template #actions>
<jet-action-message :on="form.recentlySuccessful" class="mr-3">
Saved.
</jet-action-message>
<jet-button :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Save
</jet-button>
</template>
recentlySuccessfulは更新が成功後2000ms(2秒間)だけtrueになるようにlaravel-jetsteamパッケージのの中で設定が行われています。
recentlySuccessfullはjet-action-messageタグにpropsのonで渡されているのでActionMessage.vueファイルを確認します。v-showディテクティブとpropsのonの値を利用して、onがtrueの場合のみslotタグでSaved.が表示されるように設定が行われています。Slotに渡されている値はjet-sction-messageタグの間に挿入されている文字列Saved.です。
<template>
<div>
<transition leave-active-class="transition ease-in duration-1000" leave-class="opacity-100" leave-to-class="opacity-0">
<div v-show="on" class="text-sm text-gray-600">
<slot />
</div>
</transition>
</div>
</template>
<script>
export default {
props: ['on'],
}
</script>
recentlySuccessfulの値を利用することで、SAVEボタンをクリックして更新が成功した場合にボタンの左側にSaved.のメッセージが一時的に表示されます。

form.processingについては更新中には値がtrueになるため、SAVEボタンをクリックするとopacityの設定によりボタンが薄くなり、disabledによりボタンをクリックすることができなくなります。更新内容によっては即座に完了するためボタンの変化がわかりにくいかもしれません。
このようにInertiaFormオブジェクトの変数recentlySuccessful, processingを利用してUIに変化を加えています。
まとめ
Profile Infromationを中心にInertia.jsがどのようにLaravelで利用されていることを説明してきました。
Profile Informationの部分がInartia.jsを利用してどのように処理されているのかは理解ができたかと思います。この知識を使って次は各自でInertia.jsを利用したページ作成にチャレンジしてみてください。