【Laravel + React + Inertia】を使用したSPA開発

2023/10/27
森正

こんにちは。株式会社リンクネット、ソリューション事業部の森正です。

最近弊社ではLaravel + Reactを使用した開発が増えています。
その際に Inertia.js を利用することが多いのですが、 Inertia.js を使うことで比較的簡単にSPAを実装することができます。

本記事では Inertia.js の使い方について紹介します。

■ 環境

$ sail artisan -V
Laravel Framework 9.52.0

$ sail php -v
PHP 8.2.7 (cli) (built: Jun  8 2023 15:27:40) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.7, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.7, Copyright (c), by Zend Technologies
    with Xdebug v3.2.1, Copyright (c) 2002-2023, by Derick Rethans

■ Inertia.jsの導入

今回はLaravelのスターターキット「Laravel Breeze」を使用しInertiaとReactをインストールしていきます。

$ sail composer require laravel/breeze --dev

$ sail artisan breeze:install react

$ npm install

インストール後のパッケージ一覧

$ npm list --depth=0
/home/morimasa/projects/blog/react-inertia
├── @headlessui/react@1.7.15
├── @inertiajs/react@1.0.9
├── @tailwindcss/forms@0.5.3
├── @vitejs/plugin-react@3.1.0
├── autoprefixer@10.4.14
├── axios@1.4.0
├── laravel-vite-plugin@0.7.8
├── lodash@4.17.21
├── postcss@8.4.24
├── react@18.2.0
├── react-dom@18.2.0
├── tailwindcss@3.3.2
└── vite@4.3.9

■ 画面表示

1.コンポーネントの表示

画面となるコンポーネントを用意します。
今回は下記のSampleコンポーネントを用意しました。

import { Head } from '@inertiajs/react';

const Sample = (props) => {
    return (
        <>
            <Head title="Sample" />
            <div className='m-3'>
                <p>Laravel + React + Inertiaを使用したサンプルです。</p>
            </div>
        </>
    );
}

export default Sample;

Laravelでbladeを表示する際と同様にrouteを設定します。
controllerではbladeではなくInertiaを使用しコンポーネントを返してください。

<?php

namespace App\Http\Controllers;

use Illuminate\Routing\Controller as BaseController;
use Inertia\Inertia; // Inertiaを読み込む

class Controller extends BaseController
{
    /**
     * サンプル
     *
     * @return \Inertia\Response
     */
    public function sample()
    {
        return Inertia::render('Sample'); // 表示するコンポーネントを指定
    }
}

http://localhost/sample にアクセスすると作成したコンポーネントが表示されます。

alt サンプル画面

2.propsに値を渡す

次に作成したコンポーネントに値を渡してみます。
先ほど作成したcontrollerを修正します。

第2引数に渡したい変数を指定することで、propsに値を渡すことができます。

// ... 省略

public function sample()
{
    $text = 'controllerから渡されました。';

    return Inertia::render('Sample', [
        'text' => $text
    ]);
}

コンポーネントで受け取り、画面に表示してみます。

// ... 省略

const Sample = (props) => {
    const { text } = props;

    return (
        <>
            <Head title="Sample" />
            <div className='m-3'>
                <p>Laravel + React + Inertiaを使用したサンプルです。</p>
                <p>{ text }</p>
            </div>
        </>
    );
}

controllerから渡された情報が表示されました。

alt サンプル画面

3.複数のアクションで共通の値をpropsに渡す

実際に製造を行っていると、複数のアクションから共通の値を渡したい場面も出てくると思います。
そういった場合は下記のように __construct を使用し各アクションから呼び出していました。

// ... 省略

class Controller extends BaseController
{
    private $common_text;

    public function __construct()
    {
        $this->common_text = '共通のテキストです。';
    }

    /**
     * サンプル
     *
     * @return \Inertia\Response
     */
    public function sample()
    {
        $text = 'controllerから渡されました。';

        return Inertia::render('Sample', [
            'text' => $text,
            'commonText' => $this->common_text
        ]);
    }

    /**
     * サンプル2
     *
     * @return \Inertia\Response
     */
    public function sample2()
    {
        return Inertia::render('Sample2', [
            'commonText' => $this->common_text
        ]);
    }
}

これでも良いのですが、inertiaで用意されている Middleware/HandleInertiaRequests を使用することで、簡単に実装することができます。
参考URL:https://inertiajs.com/shared-data

HandleInertiaRequests にpropsを追加

class HandleInertiaRequests extends Middleware
{
    // ... 省略

    /**
     * Define the props that are shared by default.
     *
     * @return array<string, mixed>
     */
    public function share(Request $request): array
    {
        return array_merge(parent::share($request), [
            'auth' => [
                'user' => $request->user(),
            ],
            'ziggy' => function () use ($request) {
                return array_merge((new Ziggy)->toArray(), [
                    'location' => $request->url(),
                ]);
            },
            'commonText' => '共通のテキストです。' // 追加
        ]);
    }
}

Controller から __construct を削除

// ... 省略

class Controller extends BaseController
{
    /**
     * サンプル
     *
     * @return \Inertia\Response
     */
    public function sample()
    {
        $text = 'controllerから渡されました。';

        return Inertia::render('Sample', [
            'text' => $text
        ]);
    }

    /**
     * サンプル2
     *
     * @return \Inertia\Response
     */
    public function sample2()
    {
        return Inertia::render('Sample2');
    }
}

http://localhost/sample
alt サンプル画面

http://localhost/sample2
alt サンプル画面

どちらも共通propsを受け取ることができました。

このように HandleInertiaRequests を使用することで Controller から毎回共通propsを渡す必要がなくなります。

■ リクエスト送信

1.使い方

実際はリクエスト時にデータベースを更新するような動きになると思いますが、
今回はサンプルとして、リクエストがあった場合にpropsを上書きする処理を作りました。

class Controller extends BaseController
{
    // ... 省略

    /**
     * サンプル3
     *
     * @return \Inertia\Response
     */
    public function sample3(Request $request)
    {
        $params = $request->all();

        $name = '山田 太郎';
        $address = '石川県 金沢市';

        if (!empty($params)) {
            // リクエストがあった場合、$nameと$addressを上書き
            $name = $params['name'];
            $address = $params['address'];
        }

        return Inertia::render('Sample3', compact(
            'name',
            'address'
        ));
    }
}

リクエストを送信するには
router.post(第1引数:URL, 第2引数:パラメータ, 第3引数:オプション);
で実装できます。

※第1引数でLaravelのrouteを指定する場合は ziggy.js が必要になるのでインストールしておきましょう。

npm i ziggy-js

下記の onSubmit がリクエスト送信処理のサンプルになります。

import React, { useState } from 'react';
import { Head, router } from '@inertiajs/react';
import route from 'ziggy-js';

const Sample3 = (props) => {
    const { name, address } = props;

    const [values, setValues] = useState({ name: '', address: '' });

    // stateの更新
    const changeValue = (event) => {
        setValues((values) => ({ ...values, [event.target.name]: event.target.value }))
    }

    // リクエスト送信
    const onSubmit = () => {
        router.post(route('sample3'), values, { preserveState: false });
    }

    return (
        <>
            <Head title="Sample3" />
            <div className='m-3'>
                <p>リクエスト送信サンプル</p>
            </div>
            <div className='m-3'>
                <div>■氏名</div>
                <div className='mr-3'>{ name }</div>
                <input type="text" name="name" value={ values.name } onChange={ changeValue } />
            </div>
            <div className='m-3'>
                <div>■住所</div>
                <div className='mr-3'>{ address }</div>
                <input type="text" name="address" value={ values.address } onChange={ changeValue } />
            </div>
            <div className='m-3'>
                <button type="button" onClick={ onSubmit }>更新</button>
            </div>
        </>
    );
}

export default Sample3;

実際に試してみます。
変更したい内容を入力

alt サンプル画面

「更新」押下

alt サンプル画面

氏名、住所ともに入力した内容に更新されました。

2.オプション

第三引数には様々なオプションを指定できるのですが、今回はよく使うオプションをいくつか紹介したいと思います。

● preserveState

true を指定することで状態を保持ことができます。
上の例で true を指定すると、「更新」押下後も入力した内容が保持されるようになります。

● only

指定したpropsのみ更新することができます。
上の例で[only: 'name']を指定すると、「更新」押下後、氏名のみが更新されるようになります。

● onStart(イベントコールバック)

リクエストが開始されたタイミングで実行されます。
読み込み中のインジケーター等を表示する際に便利です。

● onFinish(イベントコールバック)

リクエストが終了したタイミングで実行されます。
読み込み中のインジケーター等を非表示にする際に便利です。
※リクエストの成功、失敗に関わらず実行されるので注意してください。

● onSuccess(イベントコールバック)

リクエストが成功した場合に実行されます。
成功後に状態を更新したい場合等に便利です。

● onError(イベントコールバック)

リクエストが失敗した場合に実行されます。
バリデーションエラーを受け取る際に便利です。

※その他のオプション・イベントについては、下記に記載されています。
https://inertiajs.com/manual-visits

■ まとめ

今回は Inertia.js について紹介しました。

Inertia.js を使用することで、普段Laravelを使用している時と同じ感覚でSPAを作ることができ、とても便利ですが、
フロントエンドとバックエンドの開発者が異なる場合には向いていないため、開発方法に応じて axios 等と使い分けることが大切です。

前の記事次の記事