ReactとLaravel連携して一覧画面にデータを表示するまでの流れ

# サンプルイメージ
今回は、一覧画面にイベント情報を取得して表示するサンプルを作っていきます。

![一覧画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/72b5337c-3aab-1c9a-ee9c-e887fb386a7f.png)

この画面を作るにあたり、<b>TailwindCSSのCardコンポーネント</b>を使用しました。

https://v1.tailwindcss.com/components/cards


# このアプリの関連記事

https://qiita.com/SKY-HaYaTo/items/5d69a85bf6620e0ec78c

# フロント(React)の画面作成
## 一覧画面
メンテナンス性の観点から
`Cardコンポーネント`は、別ファイルとして作成しています。

```frontend/src/dashboard/page.tsx
import { useNavigate } from "react-router-dom"
import Navbar from "../components/navbar/page";
import axios from "axios";
import { useEffect, useState } from "react";
import Card from "../components/card/page";

export default function dashboard(){
    //ページナビゲータを用意する
    const navigate = useNavigate();
    const backToLogin = ()=>{
        navigate('/');
    }

    const[allEvent,setAllEvent] = useState<any[]>([]);        
    
    useEffect(()=>{
        const fetchEvents = async()=>{
            try{
                const response = await axios.get(
                    "http://localhost:8000/api/eventAllRead",
                    {
                        headers:{
                            'Cache-Control': 'no-cache', // キャッシュを無効化
                            Pragma: 'no-cache',
                        }
                    }
                
                );
                if(response.request.status === 200){
                    setAllEvent(response.data); // 正しいデータを取得
                }
            }catch(error){
                console.error("イベントの取得に失敗しました",error);
                alert("イベントの取得に失敗しました");
            }
        };
        fetchEvents();
    },[]);
    

    return (
        <div>
            <Navbar></Navbar>
            ダッシュボード
            {/*カードコンポーネント*/}
            <div className="cardArea grid grid-cols-3 gap-2">                                               
                {allEvent.length > 0 && allEvent.map((event)=>( 
                    <Card event={event} key={event.eventid}></Card>
                ))}                                                                                                            
            </div>
            <div>
                <button type="button" onClick={backToLogin} className="bg-blue-500 text-white font-bold px-4 py-4">
                    ログイン画面に戻る
                </button>
            </div>
        </div>
    )
} 

```

## Cardコンポーネント

```frontend/src/components/card/page.tsx
import { useNavigate } from "react-router-dom"

type Event = {
    id?:number;
    name:string;
    description:string;
    thumbnail_path:string
}

export default function Card({event}){
    //ルーティング設定
    const navigate = useNavigate();

    //更新ボタンプ家事の処理
    const EditEvent =()=>{
        navigate(`/event/eventEdition?id=${id}&name=${name}&description=${description}`);//navigate(`/event/eventEdition&id=${event.id}`);
    }

    const {id,name,description,thumbnail_path} = event;
    return (
        <div>                                   
            <div className="card bg-base-100 w-96 shadow-xl">
                <div style={{visibility:"collapse"}}>{id}</div>                               
                <div className="card bg-base-100 w-96 shadow-xl">              
                    <figure>
                        <img 
                            src={`http://localhost:8000${thumbnail_path}`} 
                            alt={name || "No Image"}
                            onError={(e)=>{
                                (e.target as HTMLImageElement).src = "/default-thumbnail.jpg";
                            }}
                        />
                    </figure>
                    <div className="card-body">
                        <h2 className="card-title font-bold text-xl mb-2">{name}</h2>
                        <p className="text-base">{description}</p>
                        <div className="card-action flex items-center justify-center mt-8">
                            <button className="bg-blue-500 text-white font-bold rounded-md px-4 py-4 mr-2" onClick={EditEvent}>
                                編集する
                            </button>
                            <button className="btn btn-accent text-white bg-rose-500 font-bold rounded-md px-4 py-4">
                                削除する
                            </button>
                        </div>                   
                    </div>
                </div>                
            </div>
        </div> 
    )
}

```


# サーバ(Laravel)のコード作成
つづいて、サーバ側の作成していきます。
今回対象になるのは、下部にある下記のコードです。


```backend/app/Http/Controllers/EventController.php
    public function readAllEvents()
    {
        $events = Event::orderBy('id','asc')->get(); // findAll()ではなくall()を使用
        return response()->json($events, 200)
                ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
                ->header('Pragma', 'no-cache')
                ->header('Expires', 'Thu, 01 Jan 1970 00:00:00 GMT');
        /*
        return response()->json([
            $event
        ],200);
        */
    }
```

また、ID値が地井さん順に全件取得するのでクエリビルダーは下記のように書いても良いです。

```sample.php
$events = Event::all()->orderBy('id', 'asc');;
```

全体のコードはこちら↓
```backend/app/Http/Controllers/EventController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;//20250110追加
use App\Models\Event;//2025.01.10追加

class EventController extends Controller
{
    public function registerEvent(Request $request)
    {
        \Log::info('リクエストデータ:', $request->all());
        $request->validate([
            'eventName' => 'required|string|max:255',
            'eventDescription' => 'required|string|max:1000',
            'eventThumbnail' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048',
        ]);

        //元のファイル名を取得する
        $originalFileName = $request->file('eventThumbnail')->getClientOriginalName();
        //ファイル名が重複しないようにタイムスタンプを追加する。
        $fileName = time().'_'.$originalFileName;

        //カスタムファイル名で画像を保存する
        $imagePath = $request->file('eventThumbnail')->storeAs('public/event_thumbnails',$fileName);
        //公開URLを生成する
        $imageUrl = Storage::url($imagePath);

        //データベースに保存する
        $event = new Event();
        $event->name = $request->eventName;
        $event->description = $request->eventDescription;
        $event->thumbnail_path = $imageUrl;
        $event->save();

        return response()->json([
            'eventName' => $event->name,
            'eventDescription' => $event->description,
            'eventThumbnail' => $imageUrl,   
        ],200);
    }

    public function editEvent(Request $request){
        \Log::info('リクエストデータ:', $request->all());
        /*これは削除しておく
        $request->validate([
            'eventId' => 'required|integer',
            'eventName' => 'required|string|max:255',
            'eventDescription' => 'required|string|max:1000',
            'eventThumbnail' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', // 画像は必須ではない場合
        ]);
                
        if ($validator->fails()) {
            return response()->json([
                'errors' => $validator->errors()
            ], 422);
        }
        */


        $targetId = $request->get('eventId');//$request->get('eventId')

        //画像ファイル処理
        $imageUrl = null;
        //$imageUrl = $request->file('eventThumbnail')->getClientOriginalName();
        
        if($request->hasFile('eventThumbnail')){
            
            //古い画像の削除
            if($imageUrl){
                // Storage::delete()で古い画像ファイルを削除
                $imagePath = public_path('storage') . '/' . basename($imageUrl); // 正しいパスに修正
                if (file_exists($imagePath)) {
                    unlink($imagePath);  // ファイル削除
                }
            }
            
            //元のファイル名を取得する
            $originalFileName = $request->file('eventThumbnail')->getClientOriginalName();
            //ファイル名が重複しないようにタイムスタンプを追加する。
            $fileName = time().'_'.$originalFileName;
            //カスタムファイル名で画像を保存する
            $imagePath = $request->file('eventThumbnail')->storeAs('public/event_thumbnails',$fileName);
            //公開URLを生成する
            $imageUrl = Storage::url($imagePath);

        }      

        //データベースの更新
        $event = Event::find($targetId);
        if (!$event) {
            return response()->json([
                'error' => 'イベントが見つかりません'
            ], 404);
        }

        $event->name = $request->eventName;
        $event->description = $request->eventDescription;
        $event->thumbnail_path = $imageUrl;
        if($imageUrl){
            $event->thumbnail_path = $imageUrl;
        }

        $event->save();
               
        return response()->json([
            'eventName' => $event->name,
            'eventDescription' => $event->description,
            'eventThumbnail' => $event->thumbnail_path,   
        ],200);
    }

    public function readAllEvents()
    {
        $events = Event::orderBy('id','asc')->get(); // findAll()ではなくall()を使用
        return response()->json($events, 200)
                ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
                ->header('Pragma', 'no-cache')
                ->header('Expires', 'Thu, 01 Jan 1970 00:00:00 GMT');
        /*
        return response()->json([
            $event
        ],200);
        */
    }

}


```

以上です。