第13回 Laravel10 環境構築メモ(Laravel+Typescript+React+inertiaでCreateをグレードアップしてみる)

はじめに

前回、登録画面を作成したのですが、今日は、それを少し改造してみます。

  • FormRequestを使ってValidateする(ChatGPTに聞いたらおすすめされた)

  • 成功して一覧画面に戻った時にFlashメッセージを表示する

  • ReactのTypescriptをしっかりと修正する(Interfaceとか定義してなかった&型指定してなかったので、叱られてた)

ちなみに、今更ながらGithub Copilotを使い始めたのですが、若干、生産性が上がった気がします。若干ね。コード予測してtabで補完できるのが、たまに、いい感じですね。

FormRequestを作成

artisanのmakeを使ってFormRequestを作成します。

php artisan make:request Greeting/StoreRequest

出来たファイルを修正します。authorizeのreturnをtrueにしないと画面表示でエラーになるので、お忘れなく。

  • app/Http/Requests/Greeting/StoreRequest.php

<?php

namespace App\Http\Requests\Greeting;

use Illuminate\Foundation\Http\FormRequest;

class StoreRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'country' => ['required', 'max:200'],
            'message' => ['required', 'max:200'],
        ];
    }
}

Controllerの修正

今回、修正するのはstoreだけです。引数をStoreRequestに変更してください。あと、flashメッセージを表示したいので、withでメッセージも追加します。

  • app/Http/Controllers/GreetingController.php

    /**
     * Store a newly created resource in storage.
     */
    public function store(StoreRequest $request)
    {
        Greeting::create($request->validated());
        return to_route('greetings.index')->with('flash', 'Greeting created successfully');
    }

HandleInertiaRequestsの修正

ここから若干、難しくなるのですが、React側でusePageを使ってshared data(いろんなページで値を取得したいようなものを入れとく時にたぶん使うみたいなんですが、詳しくは、本家のこのページ見てください)を取得する事ができるのですが、今回、flashメッセージをそこに追加してみました。Controllerでsessionに突っ込んでるのを一回取り出して、shared dataに突っ込みなおすという事をしています。

  • app/Http/Middleware/HandleInertiaRequests.php

    public function share(Request $request): array
    {
        return [
            ...parent::share($request),
            'auth' => [
                'user' => $request->user(),
            ],
            'ziggy' => fn () => [
                ...(new Ziggy)->toArray(),
                'location' => $request->url(),
            ],
            'flash' => fn () => $request->session()->get('flash'),
        ];
    }

resources/js/types/index.d.tsの修正

HandleInertiaRequestsの修正だけでもReact側でデータは取れるんですが、そのままだとESLintで叱られるのでPagePropsを拡張します。PagePropsは、usePageでpropsを取得するときのtypeになります。
↓な感じで既に拡張されてauthがいらっしゃるんですが、さらにflashを追加してみます。

  • resources/js/types/index.d.ts

export type PageProps<
  T extends Record<string, unknown> = Record<string, unknown>
> = T & {
  auth: {
    user: User
  }
  flash: string
}

あとは、一覧画面と登録画面をESLintで叱られないようにしっかり修正しつつ、flashメッセージが一覧画面で表示されるようにします。

  • resources/js/Pages/Greeting/index.tsx

import { Head, usePage } from '@inertiajs/react'
import { type FC } from 'react'
import { type PageProps } from '@/types'
interface Props {
  greetings: {
    data: Greeting[]
  }
  title: string
}
interface Greeting {
  id: number
  country: string
  message: string
}

const Index: FC<Props> = ({ greetings, title }) => {
  const { flash } = usePage<PageProps>().props
  return (
    <>
      <Head title={title} />
      {flash !== null && (
        <h1 className="bg-green-400 text-white">{flash}</h1>
      )}
      {greetings.data.map((greeting: Greeting) => (
        <div key={greeting.id}>
          <h2>
            #{greeting.id} - {greeting.country}
          </h2>
          <p>{greeting.message}</p>
        </div>
      ))}
    </>
  )
}
export default Index
  • resources/js/Pages/Greeting/create.tsx

import { Head, router, usePage } from '@inertiajs/react'
import React, { type FC, useState } from 'react'
import GuestLayout from '@/Layouts/GuestLayout'
import InputLabel from '@/Components/InputLabel'
import TextInput from '@/Components/TextInput'
import PrimaryButton from '@/Components/PrimaryButton'
import InputError from '@/Components/InputError'

const Create: FC = () => {
  const { errors } = usePage().props
  const [values, setValues] = useState({
    country: '',
    message: ''
  })

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
    const key = e.target.id
    const value = e.target.value
    setValues(values => ({
      ...values,
      [key]: value
    }))
  }

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
    e.preventDefault()
    router.post('/greetings', values)
  }

  return (
    <GuestLayout>
    <Head title='Greeting - Create' />
    <form onSubmit={handleSubmit}>
      <div>
        <InputLabel htmlFor="country">Country:</InputLabel>
        <TextInput id="country" value={values.country} onChange={handleChange} className="mt-1 block w-full"/>
        <InputError message={errors.country} className="mt-2" />
      </div>
      <div>
        <InputLabel htmlFor="message">Message:</InputLabel>
        <TextInput id="message" value={values.message} onChange={handleChange} className="mt-1 block w-full"/>
        <InputError message={errors.message} className="mt-2" />
      </div>
      <div className="flex items-center justify-end mt-4">
        <PrimaryButton className="ms-4" type="submit">
          Submit
        </PrimaryButton>
      </div>
    </form>
    </GuestLayout>
  )
}
export default Create

実際に登録して、flashメッセージが表示されるか確認してみる

登録画面(http://localhost/greetings/create)で適当に値を入力してSUBMITしてみてください。下記の様に画面上部にメッセージが表示されればOKです。

http://localhost/greetings

さいごに

これ余談なのですが、artisanのコマンドを実行するときに毎度、Xdebugのこのエラーが出て邪魔でした。

Xdebug: [Step Debug] Time-out connecting to debugging client, waited: 200 ms

どうすると出なくなるかというとphp.iniを下記の通り修正してください。

# xdebug.start_with_request=yes
xdebug.start_with_request = trigger

試してないのですが、Xdebug helperとかをChromeに入れないとdebug動かなくなるかもですが、気になる人は修正してみてください。

参考になったサイト


この記事が気に入ったらサポートをしてみませんか?