【7日目】React初学者が1週間でどこまで出来るか挑戦(CRUD総まとめ)
はじめに
React学習1週間チャレンジ7日目(最終日)です。これまでReactの公式ドキュメントを中心に基礎的な部分を学んできました。
「どこまで出来るか挑戦」の結果は以下の通りです。Inertiaを使う前提ですが基礎を理解した上でCRUD処理は実装できました。
振り返り:
- Breezeパッケージを使ってInertiaとReactをLaravelに導入
- Breezeを使わず、InertiaとReactを導入
- Inertiaの使い方
- Reactの基礎をドキュメントで学習(2チャプター完了)
- CRUD処理の実装【今回】
今回は1週間の復習も兼ねて、より実践的なケースを想定したCRUD処理を作ってみました。
環境:
- Mac M1 2020
- Docker:
24.0.2
- PHP:
8.2.x
- Laravel:
10.x
- MySQL:
5.7
- nginx:
1.25.1
- React:
18.2.0
実践: 顧客マスタのCRUDを実装
顧客マスタを想定してCRUD処理を作ってみます。(user_id
は前回までに作ったusersテーブルと紐付けます)今回は間に合いませんでしたが、user_id
を選択するために非同期でユーザー検索モーダル画面も今後実装する予定です。
Index: 一覧表示
customersテーブルを定義
Schema::create('customers', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id');
$table->string('name');
$table->string('address');
$table->string('phone_number');
$table->string('email');
$table->timestamps();
});
Customerモデルの設定
// app/Models/Customer.php
protected $fillable = [
'user_id',
'name',
'address',
'phone_number',
'email',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
ルーティング定義
// routes/web.php
Route::get('customers', [CustomerController::class, 'index'])->name('customers.index');
index
メソッドを定義(CustomerController)
// app/Http/Controllers/CustomerController.php
use App\Models\Customer;
use Inertia\Inertia;
...
public function index()
{
return Inertia::render('Customer/Index', [
'customers' => Customer::with('user')->get(),
]);
}
Viewの作成(Index.jsx
)
// resources/js/Pages/Customer/Index.jsx
import AppLayout from '@/Layouts/AppLayout';
export default function Index({ customers }) {
return (
<AppLayout>
<h1 className="title title-h1">顧客マスタ</h1>
<table className="table">
<thead className="table-header">
<tr>
<th className="th-cell">ID</th>
<th className="th-cell">名前</th>
<th className="th-cell">住所</th>
<th className="th-cell">電話番号</th>
<th className="th-cell">E-mail</th>
<th className="th-cell">担当ユーザーID : 名前</th>
</tr>
</thead>
<tbody>
{customers.map(customer => (
<tr key={customer.id}>
<td className="td-cell">{customer.id}</td>
<td className="td-cell">{customer.name}</td>
<td className="td-cell">{customer.address}</td>
<td className="td-cell">{customer.phone_number}</td>
<td className="td-cell">{customer.email}</td>
<td className="td-cell">{customer.user_id} : {customer.user.name}</td>
</tr>
))}
</tbody>
</table>
</AppLayout>
);
}
コードを管理しやすくするために、テーブルはコンポーネントに切り出します。CustomerTable
コンポーネントを作成:
// resources/js/Pages/Customer/Partials/CustomerTable.jsx
export default function CustomerTable({ customers }) {
return (
<table className="table">
<thead className="table-header">
<tr>
<th className="th-cell">ID</th>
<th className="th-cell">名前</th>
<th className="th-cell">住所</th>
<th className="th-cell">電話番号</th>
<th className="th-cell">E-mail</th>
<th className="th-cell">担当ユーザーID : 名前</th>
</tr>
</thead>
<tbody>
{customers.map(customer => (
<tr key={customer.id}>
<td className="td-cell">{customer.id}</td>
<td className="td-cell">{customer.name}</td>
<td className="td-cell">{customer.address}</td>
<td className="td-cell">{customer.phone_number}</td>
<td className="td-cell">{customer.email}</td>
<td className="td-cell">{customer.user_id} : {customer.user.name}</td>
</tr>
))}
</tbody>
</table>
);
}
Index.jsx
でCustomerTable
コンポーネントを読み込み:
// resources/js/Pages/Customer/Index.jsx
import AppLayout from '@/Layouts/AppLayout';
import CustomerTable from "./Partials/CustomerTable";
export default function Index({ customers }) {
return (
<AppLayout>
<h1 className="title title-h1">顧客マスタ</h1>
<CustomerTable customers={customers} />
</AppLayout>
);
}
一覧画面のページ完成。
Create: 登録フォーム
ルーティング定義
// routes/web.php
Route::get('customers/create', [CustomerController::class, 'create'])->name('customers.create');
コントローラーの設定
// app/Http/Controllers/CustomerController.php
use Inertia\Inertia;
...
public function create()
{
return Inertia::render('Customer/Create');
}
Index.jsx
の適当な場所に登録フォームへのリンクを作成。tighten/ziggyを使ってJSX内でLaravelのroute
へルパを使用している。
// resources/js/Pages/Customer/Index.jsx
<a href={route('customers.create')} className="btn btn-create">新規登録</a>
登録フォーム(Create.jsx
)を作成。InertiaのuseFormへルパを使用。
// resources/js/Pages/Customer/Create.jsx
import AppLayout from '@/Layouts/AppLayout';
import { useForm } from '@inertiajs/react';
export default function Create() {
const { data, setData, post, errors, processing, isDirty } = useForm({
user_id: '',
name: '',
address: '',
phone_number: '',
email: '',
});
const submit = (e) => {
e.preventDefault();
post(route('customers.store'))
};
return (
<AppLayout>
<h1 className="title title-h1">顧客 登録</h1>
<form onSubmit={submit}>
<div className="row">
<input type="text"
name="name"
value={data.name}
onChange={(e) => setData('name', e.target.value)}
className="input-field"
placeholder="名前"
/>
<div className='invalid-feedback'>{errors.name}</div>
</div>
<div className="row">
<input type="text"
name="address"
value={data.address}
onChange={(e) => setData('address', e.target.value)}
className="input-field"
placeholder="住所"
/>
<div className='invalid-feedback'>{errors.address}</div>
</div>
<div className="row">
<input type="text"
name="phone_number"
value={data.phone_number}
onChange={(e) => setData('phone_number', e.target.value)}
className="input-field"
placeholder="電話番号"
/>
<div className='invalid-feedback'>{errors.phone_number}</div>
</div>
<div className="row">
<input type="email"
name="email"
value={data.email}
onChange={(e) => setData('email', e.target.value)}
className="input-field"
placeholder="E-mail"
/>
<div className='invalid-feedback'>{errors.email}</div>
</div>
<div className="row">
<input type="number"
name="user_id"
value={data.user_id}
onChange={(e) => setData('user_id', e.target.value)}
className="input-field"
placeholder="担当ユーザーID"
/>
<div className='invalid-feedback'>{errors.user_id}</div>
</div>
<button className='btn btn-store' disabled={!isDirty || processing}>
登録
</button>
</form>
</AppLayout>
);
}
Store: 登録処理
ルーティング
// routes/web.php
Route::post('customers/store', [CustomerController::class, 'store'])->name('customers.store');
バリデーション
// app/Http/Requests/CustomerStoreRequest.php
public function rules(): array
{
return [
'user_id' => 'required|integer|exists:users,id',
'name' => 'required|string|max:255',
'address' => 'required|string',
'phone_number' => 'required|string|max:20',
'email' => 'required|email|unique:customers,email',
];
}
コントローラ
// app/Http/Controllers/CustomerController.php
use App\Http\Requests\CustomerStoreRequest;
...
public function store(CustomerStoreRequest $request)
{
$inputs = $request->only([
'user_id',
'name',
'address',
'phone_number',
'email',
]);
Customer::create($inputs);
return redirect()->route('customers.index');
}
Edit: 編集フォーム
ルーティング定義
// routes/web.php
Route::get('customers/{customer}/edit', [CustomerController::class, 'edit'])->name('customers.edit');
コントローラ
// app/Http/Controllers/CustomerController.php
use App\Models\Customer;
use Inertia\Inertia;
...
public function edit(Customer $customer)
{
return Inertia::render('Customer/Edit', [
'customer' => $customer,
]);
}
Viewの作成。Createとの違いは、
- 初期値として
data
にpropsで受け取ったcustomer
の値をセットしていること - フォーム送信先を
patch(route('customers.update', customer.id))
にする
import AppLayout from '@/Layouts/AppLayout';
import { useForm } from '@inertiajs/react';
export default function Edit({ customer }) {
const { data, setData, patch, errors, processing, isDirty } = useForm({
user_id: customer.user_id,
name: customer.name,
address: customer.address,
phone_number: customer.phone_number,
email: customer.email,
});
const submit = (e) => {
e.preventDefault();
patch(route('customers.update', customer.id))
};
// 以下省略。Create.jsxと同じ。
Update: 更新処理
ルーティング
// routes/web.php
Route::patch('customers/{customer}', [CustomerController::class, 'update'])->name('customers.update');
バリデーション。CustomerStoreRequest
と異なるのはunique
ルールのignore
処理のみ。
// app/Http/Requests/CustomerUpdateRequest.php
use Illuminate\Validation\Rule;
...
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('customers')->ignore($this->route('customer')), // ここだけ
],
// 他は省略
コントローラ
// app/Http/Controllers/CustomerController.php
use App\Http\Requests\CustomerUpdateRequest;
...
public function update(CustomerUpdateRequest $request, Customer $customer)
{
$inputs = $request->only([
'user_id',
'name',
'address',
'phone_number',
'email',
]);
$customer->update($inputs);
return redirect()->route('customers.index');
}
Delete: 削除
ルーティング
// routes/web.php
Route::delete('customers/{customer}', [CustomerController::class, 'destroy'])->name('customers.destroy');
コントローラ
// app/Http/Controllers/CustomerController.php
public function destroy(Customer $customer)
{
$customer->delete();
return redirect()->route('customers.index');
}
Edit.jsx
に削除フォームを追加する。InertiaのForm helperのdestroy
メソッドを使用。第二引数には色々なvisit optionを指定することができる。ここでは、onBefore
というコールバックを指定して、結果がfalse
の時は処理をキャンセルしている。
- visit option: Manual visits - Inertia.js
- Form helper: Forms - Inertia.js
// resources/js/Pages/Customer/Edit.jsx
const { delete:destroy, ... } = useForm({ ... })
const deleteCustomer = (e) => {
e.preventDefault();
destroy(route('customers.destroy', customer), {
onBefore: () => confirm('本当に削除しますか?'),
});
}
return (
{/* 省略 */}
<form onSubmit={deleteCustomer}>
<button className="btn btn-delete">削除</button>
</form>
);
以上で基本的なCRUD処理は完成。
おわりに
今回Laravel、Inertiaを使用して進めてきましたが、Inertiaのお陰でかなり簡単にコードが書けている印象を受けました。そのため、React自体の理解は今後もっと深めていく必要があるとは感じてます。
1週間チャレンジは終わりですが、もちろんReactの勉強は今後も続けていきます。今後は実際にアプリ開発を通して知識を身につけていこうと思ってます。