5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Inertia.jsのPartial reloadsを理解したい

Posted at

■ Partial reloads

Partial Reloadsは、Inertia.jsの強力な機能の一つで、ページの特定の部分だけを更新することができます。これにより、全ページを再読み込みする必要がなくなり、ユーザーエクスペリエンスが向上します。たとえば、ユーザーがフォームを送信した後にリストのみを更新したい場合や、特定のコンポーネントのデータを更新したい場合に便利です。

本記事では、Laravel×ReactのコードベースでPartial reloadsの動きを確認します。

■ Partial reloadsでページの一部だけを更新する

ユーザー検索機能を例にPartial reloadsでページの一部だけを更新する場合の実装を確認していきます。Inertia自体にそこまで馴染みがない方もいると思うので、順を追って丁寧に機能を実装していきます。

▼ 準備

まずはユーザー検索機能を付けずに、Inertiaを利用したシンプルなユーザー一覧ページを用意します。

resouces/js/Pages/User/List.tsx
type Props = {
  users: {
    id: number;
    name: string;
  }[];
};

export default function List(props: Props) {
  const { users } = props;

  return (
    <div>
      <h1>ユーザー一覧</h1>
      <ul>
        {users.map((user) => <li key={user.id}>{user.name}</li>)}
      </ul>
    </div>
  );
}
app/Http/Controllers/User/List/Controller.php
<?php

namespace App\Http\Controllers\User\List;

use Inertia\Inertia;
use App\Models\User;

class Controller {
    public function __invoke() {
        $users = User::all();

        return Inertia::render("User/List", [
            "users" => $users,
        ]);
    }
}
routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\User;

Route::get('/user', User\List\Controller::class);

image.png

▼ ユーザー検索機能追加

この一覧ページにユーザーの名前検索機能を追加していきます。検索時にPartial reloadsすることで、ページを再読み込みすることなく、ユーザー名部分だけ更新することができます。

まずは、ユーザー一覧のリクエストクラスを追加し、コントローラー部分を修正して、検索処理を追加します。

app/Http/Controllers/User/List/Request.php
<?php

namespace App\Http\Controllers\User\List;

use Illuminate\Foundation\Http\FormRequest;

class Request extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'name' => 'sometimes|string|max:255',
        ];
    }
}

app/Http/Controllers/User/List/Controller.php
<?php
  
namespace App\Http\Controllers\User\List;
  
use Inertia\Inertia;
use App\Models\User;
  
class Controller {
-   public function __invoke() {
+   public function __invoke(Request $request) {

+       $search_name = $request->query("name");

-       $users = User::all();
+       $users = User::query()
+           ->when($search_name, fn ($query, $search_name) => $query->where("name", "like", "%{$search_name}%"))
+           ->get();

        return Inertia::render("User/List", [
            "users" => $users,
        ]);
    }
}

次に、フロントエンド側の準備です。検索フォームコンポーネントを用意します。

resources/js/Components/User/List/SearchForm.tsx
import { useState } from 'react';

type Props = {
  handleSearch: (name: string) => void;
}

export default function SearchForm(props: Props) {
  const { handleSearch } = props;

  const [name, setName] = useState('');

  const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setName(e.target.value);
  };

  return (
    <div>
      <input type="text" value={name} onChange={handleNameChange} />
      <button onClick={() => handleSearch(name)}>検索</button>
    </div>
  );
}

これを使用して、検索機能を完成させます。

resouces/js/Pages/User/List.tsx
+ import SearchForm from "@/Components/User/List/SearchForm";
+ import { router } from "@inertiajs/react";

type Props = {
  users: {
    id: number;
    name: string;
  }[];
};

export default function List(props: Props) {
  const { users } = props;

+ const handleSearch = (name: string) => {
+   router.reload({
+     data: { name }, // クエリパラメータにnameを追加
+     only: ['users'], // usersのみ再取得 👈 Partial reloads!
+   })
+ };

  return (
    <div>
      <h1>ユーザー一覧</h1>
+     <SearchForm handleSearch={handleSearch} />
      <ul>
        {users.map((user) => <li key={user.id}>{user.name}</li>)}
      </ul>
    </div>
  );
}

画面収録 2024-02-12 0.09.22.gif

■ Lazy data evaluation

Lazy data evaluationについて解説します。説明のために、次のような2つのサイコロの目を振るページを用意しました。

画面収録 2024-02-12 0.46.18.gif

resources/js/Dice.tsx
import { router } from "@inertiajs/react";

type Props = {
  dice1: number;
  dice2: number;
};

export default function Dice(props: Props) {
  const { dice1, dice2 } = props;

  const rollDice1 = () => {
    router.reload({
      only: ['dice1'],
    });
  };

  const rollDice2 = () => {
    router.reload({
      only: ['dice2'],
    });
  }

  return (
    <>
      <p>サイコロ1: {dice1}</p>
      <button onClick={rollDice1}>サイコロ1を振り直す</button>
      <p>サイコロ2: {dice2}</p>
      <button onClick={rollDice2}>サイコロ2を振り直す</button>
    </>
  );
}
app/Http/Controllers/Dice/Controller.php
<?php

namespace App\Http\Controllers\Dice;

use Illuminate\Support\Facades\Log;
use Inertia\Inertia;

class Controller {
    public function __invoke() {
        $roll1 = function () {
            Log::info("サイコロ1が振られました");
            return random_int(1,6);
        };

        $roll2 = function () {
            Log::info("サイコロ2が振られました");
            return random_int(1,6);
        };

        return Inertia::render("Dice", [
            "dice1" => $roll1(),
            "dice2" => $roll2(),
        ]);
    }
}
routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Dice;

Route::get("/dice", Dice\Controller::class);

さて、どちらもPartial reloadsでデータを取得しているので、サイコロ1を振り直せばサイコロ1の目だけが変わります。サイコロ2についても同様です。しかし、ログを確認すると、どちらのサイコロも振られています。

image.png

つまり、現状だと、$roll1$roll2がともに実行されてから、どのデータだけを返却するのか評価していることになります。

app/Http/Controllers/Dice/Controller.php
// ...

class Controller {
    public function __invoke() {
        // ...

        return Inertia::render("Dice", [
            "dice1" => $roll1(), // 👈  $roll1が実行されてからdice1を返すかどうか評価している
            "dice2" => $roll2(), // 👈  $roll2が実行されてからdice2を返すかどうか評価している
        ]);
    }
}

サイコロ1のデータしか必要ないときに、サイコロ2も振ってしまうのは無駄ですから、振らないようにしたいです。そこで、どちらサイコロのデータが必要なのか先に判断し、そのサイコロだけのデータを取得するようにします。このように、データが必要になったときだけ、データ取得処理が実行されるようにする方法がLazy data evaluationです。これができれば無駄な処理が実行されずに済みます。

実装方法は簡単で、データ取得処理をクロージャーで包むだけです。

app/Http/Controllers/Dice/Controller.php
// ...

class Controller {
    public function __invoke() {
        // ...

        return Inertia::render("Dice", [
            "dice1" => fn () => $roll1(), // 👈  $dice1を返す場合のみ$roll1が実行される
            "dice2" => fn () => $roll2(), // 👈  $dice2を返す場合のみ$roll2が実行される
        ]);
    }
}

こうすることで、必要なデータ取得処理だけが実行できるようになります。

image.png

さらに、初期表示時には、データを取得したくないというケースもあるでしょう。そういう場合はさらに、Inertia::lazy()でデータ取得部分をラッピングします。

app/Http/Controllers/Dice/Controller.php
// ...

class Controller {
    public function __invoke() {
        // ...

        return Inertia::render("Dice", [
            "dice1" => Inertia::lazy(fn () => $roll1()),
            "dice2" => Inertia::lazy(fn () => $roll2()),
        ]);
    }
}

画面収録 2024-02-12 1.19.45.gif

5
1
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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?