inertia.jsでinline編集
これはlaravel bootcampでも見られたものであるが
まあ、やってみよう
事前準備
artisan make:model Demo -mrcf
とか。今回はdemosテーブルにbodyというテーブルを1つ作って編集するだけ
public function up(): void
{
Schema::create('demos', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->timestamps();
});
}
factory
public function definition(): array
{
return [
'body' => fake()->realText(100),
];
}
seed。ポイントとしては2う以上作る。2つ以上なら何個でもいいが今回は3とした。
\App\Models\Demo::factory(3)->create();
テキストデーターの表示
今回はdemos.indexで全てやることにする。一応resourceルートになっているのでroutes.webは以下のようにしてあるもんとしよう
Route::resource('demos', DemoController::class);
DemoController@index
inertiaなのでこんな感じだろう。
use Inertia\Inertia;
use Inertia\Response;
class DemoController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index(): Response
{
$demos = Demo::latest()->get();
return Inertia::render('Demos/Index', [
'demos' => $demos,
]);
}
Demos/Index.jsx
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import { Head } from '@inertiajs/react'
export default function DemoIndex({ auth, demos }) {
return (
<AuthenticatedLayout
user={auth.user}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Demos
</h2>
}
>
<Head title="Demos" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div className="p-6 text-gray-900">
{demos.map((demo, index) => (
<div key={index} className="bg-white border border-gray-300 rounded-lg shadow-sm p-4 mb-4 hover:shadow-md transition-shadow duration-200">
<p className="text-gray-900">{demo.body}</p>
</div>
))}
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}
まこんなもんだろう。
編集ボタンつけたりコンポーネント化したり
このままの段階だとstateが管理できないのだが、まあその管理できなさも含めてとりあえずコンポーネントなしで作っていって後で分けよう。
{demos.map((demo, index) => (
<div key={index} className="bg-white border border-gray-300 rounded-lg shadow-sm p-4 mb-4 hover:shadow-md transition-shadow duration-200 relative flex flex-col justify-between">
<p className="text-gray-900">
{demo.body}
</p>
<div className="mt-auto flex justify-end">
<PrimaryButton>編集</PrimaryButton>
</div>
</div>
))}
こんな感じでボタンを付ける
stateを動かす
import React, { useState } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import { Head } from '@inertiajs/react'
import PrimaryButton from '@/Components/PrimaryButton';
export default function DemoIndex({ auth, demos }) {
const [isEditing, setIsEditing] = useState(false);
などと書いておいてisEditing flagを管理するようにする
<p className="text-gray-900">
{demo.body}
</p>
<div className="mt-auto flex justify-end">
{!isEditing ? (
<PrimaryButton onClick={() => setIsEditing(true)}>編集</PrimaryButton>
) : (
<div>
<PrimaryButton className="mr-2">保存</PrimaryButton>
<PrimaryButton onClick={() => setIsEditing(false)}>キャ>ンセル</PrimaryButton>
</div>
)}
</div>
このようにする。すると以下のようにstateが変化してボタンが変化するだろう
今、投稿ごとにstateを管理していないので全てのボタンに対してflagが適用されている。これを投稿ごとにstate管理する場合通常コンポーネント化する必要がある。
コンポーネント化する
これは resources/js/Components/DemoItem.jsx に作成するものとする
import React, { useState } from 'react';
import PrimaryButton from '@/Components/PrimaryButton';
export default function DemoItem({ demo }) {
const [isEditing, setIsEditing] = useState(false);
return (
<div className="bg-white border border-gray-300 rounded-lg shadow-sm p-4 mb-4 hover:shadow-md transition-shadow duration-200 relative flex flex-col justify-between">
<p className="text-gray-900">
{demo.body}
</p>
<div className="mt-auto flex justify-end">
{!isEditing ? (
<PrimaryButton onClick={() => setIsEditing(true)}>編集</PrimaryButton>
) : (
<div>
<PrimaryButton className="mr-2">保存</PrimaryButton>
<PrimaryButton onClick={() => setIsEditing(false)}>キャンセル</PrimaryButton>
</div>
)}
</div>
</div>
);
}
このように切り離したら本体は
resources/js/Pages/Demos/Index.jsx
import React, { useState } from 'react';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'
import { Head } from '@inertiajs/react'
import PrimaryButton from '@/Components/PrimaryButton';
import DemoItem from '@/Components/DemoItem';
export default function DemoIndex({ auth, demos }) {
const [isEditing, setIsEditing] = useState(false);
return (
<AuthenticatedLayout
user={auth.user}
header={
<h2 className="font-semibold text-xl text-gray-800 leading-tight">
Demos
</h2>
}
>
<Head title="Demos" />
<div className="py-12">
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div className="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div className="p-6 text-gray-900">
{demos.map((demo, index) => (
<DemoItem key={index} demo={demo} />
))}
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
)
}
こんな感じになるだろう。
そうすると、投稿ごとにstateが分けられるから
1つずつ処理するのもまた可能ということになるわけだ。
デザインを作りこんでいく
まず、inertia.jsでformを扱う場合はuseFormするので、これをimportしておく。この辺りの話は全てcomponentでの話となる
resources/js/Components/DemoItem.jsx
import React, { useState } from 'react';
import PrimaryButton from '@/Components/PrimaryButton';
import DangerButton from '@/Components/DangerButton';
import { useForm } from '@inertiajs/react';
export default function DemoItem({ demo }) {
const [isEditing, setIsEditing] = useState(false);
const { data, setData, patch, clearErrors, reset, errors } = useForm({
body: demo.body,
});
const submit = (e) => {
e.preventDefault();
};
このようにbodyの初期値にはdemo.bodyを投入しておく
あと、キャンセルをDangerButtonとした。submit関数は今は何もしない。
残りのjsxの部分はまあこんな感じ
return (
<div className="bg-white border border-gray-300 rounded-lg shadow-sm p-4 mb-4 hover:shadow-md transition-shadow duration-200 relative flex flex-col justify-between">
{isEditing ?
<form onSubmit={submit}>
<textarea value={data.body}
onChange={e => setData('body', e.target.value)}
className="mt-4 w-full text-gray-900 border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"></textarea>
<PrimaryButton className="mr-2">保存</PrimaryButton>
</form>
: <p className="text-gray-900">{demo.body}</p>
}
<div className="mt-auto flex justify-end">
{!isEditing ? (
<PrimaryButton onClick={() => setIsEditing(true)}>編集</PrimaryButton>
) : (
<div>
<DangerButton onClick={() => setIsEditing(false)}>キャンセル</DangerButton>
</div>
)}
</div>
</div>
);
まあ一見よさそうだ。
実際に保存していく
まあ保存というかupdateというかapp/Http/Controllers/DemoController.php
public function update(Request $request, Demo $demo)
{
dd($request->all());
}
いつものようにrequestを待機しておく。
で
const submit = (e) => {
e.preventDefault();
patch(route('demos.update', demo.id), { onSuccess: () => setIsEditing(false) });
};
submitをこんな風にすればupdateにリクエストがぶん投げられるはずだ
全てがokっぽいから更新してredirectしてあげる
public function update(Request $request, Demo $demo): RedirectResponse
{
$demo->body = $request->body;
$demo->update();
return redirect(route('demos.index'));
}
ちょっとviewを微調整したぞい
import React, { useState } from 'react';
import PrimaryButton from '@/Components/PrimaryButton';
import DangerButton from '@/Components/DangerButton';
import { useForm } from '@inertiajs/react';
export default function DemoItem({ demo }) {
const [isEditing, setIsEditing] = useState(false);
const { data, setData, patch, clearErrors, reset, errors } = useForm({
body: demo.body,
});
const submit = (e) => {
e.preventDefault();
patch(route('demos.update', demo.id), { onSuccess: () => setIsEditing(false) });
};
return (
<div className="bg-white border border-gray-300 rounded-lg shadow-sm p-4 mb-4 hover:shadow-md transition-shadow duration-200 relative flex flex-col justify-between">
{isEditing ?
<form onSubmit={submit}>
<textarea value={data.body}
onChange={e => setData('body', e.target.value)}
className="mt-4 w-full h-32 text-gray-900 border-gray-300 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 rounded-md shadow-sm"></textarea>
<PrimaryButton className="mr-2">保存</PrimaryButton>
</form>
: <p className="text-gray-900 whitespace-pre-wrap">{demo.body}</p>
}
<div className="mt-auto flex justify-end">
{!isEditing ? (
<PrimaryButton onClick={() => setIsEditing(true)}>編集</PrimaryButton>
) : (
<div>
<DangerButton onClick={() => setIsEditing(false)}>キャンセル</DangerButton>
</div>
)}
</div>
</div>
);
}
まあ実際はerror処理だとか認証/認可とかいろいろあるとは思います。
この記事が気に入ったらサポートをしてみませんか?