ReactとLaravel連携してデータ削除するまでの流れ

# つくったサンプル

## Githubリポジトリ

https://github.com/SKY-HaYaTo/reactLaravelEventApps/tree/main


<b>一覧画面で削除対象のイベント<font color="red">(赤枠)を選択します。</font></b>
![削除1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/e35ff673-faf0-89ad-a018-2385397f4404.png)

<b>選削除ボタンを押下します</font></b>
![削除2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/142c2673-b7f1-bc7a-2f54-009611e5a585.png)

<b>正常に削除が完了すると。自動的に一覧画面に遷移します</font></b>
![削除3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/d58f2ad8-48f9-622b-ad14-72c5d562f299.png)


<b>データベースを見ても、対象のデータが削除されていますね。</font></b>
![削除4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/a802e6e2-0833-ca6a-f7b3-de5907386d92.png)

# ディレクトリ構造
ディレクトリ構造は、下記のようにしました。
```
ReactLaravelProject
├─ backend
│  ├─ app
│  │  ├─ Http
│  │  │  └─ Controllers
│  │  │     ├─ EventController.php
│  │  │     └─ RegisterController.php
│  │  └─ Models
│  │     └─ Event.php
│  └─ Routes
│     └─ api.php
└─ frontend
      └─ src
         ├─ components
         │  ├─ card
         │  │  └─ page.tsx
         │  └─ navbar
         │     └─ page.tsx
         ├─ dashboard  
         │     └─ page.tsx
         ├─ event
         │     ├─ eventDelete
         │     │  └─ page.tsx
         │     ├─ eventEdition
         │     │  └─ page.tsx
         │     └─ eventRegistration
         │        └─ page.tsx
         ├─ App.css
         ├─ App.tsx
         ├─ index.css
         ├─ index.tsx
         ├─ main.tsx
         └─ Tailwind.Config.js
```

# フロント(React)側の削除画面を作る

:::note info
axios.post で targetId を JSON ボディとして送る場合、targetId の値を JSON オブジェクトの一部として送るようにしましょう。

```/frontend/src/event/eventDelete/page.tsx
await axios.post('http://localhost:8000/api/eventDelete',{eventId:targetId});
```

:::


```/frontend/src/event/eventDelete/page.tsx
import axios from "axios";
import { useNavigate,useSearchParams } from "react-router-dom"

export default function DeleteEvent(){
    //ルーティング設定する
    const navigate = useNavigate();

    //削除するボタンクリック時の処理
    const deleteEvent = async(e:React.FormEvent)=>{
        e.preventDefault();
        //alert("策k叙しますが、よろしいですか？");
        const response = await axios.post('http://localhost:8000/api/eventDelete',{eventId:targetId});
        if(response.request.status === 200){
            
            navigate('/dashboard');
        }else{
            alert("削除に失敗しました。");
        }
    }

    //戻るボタンクリックしたときの処理
    const backToDashboard = ()=>{
        navigate("/dashboard");
    }

    //URLクエリからデータを取得する
    const [searchParams] = useSearchParams();
    const targetId = searchParams.get('id');
    const targetName = searchParams.get('name');
    const targetDescription = searchParams.get('description'); 

    return (
        <div className="shadow-md border border-gray-500 rounded-md">
            <h2>削除ページ</h2>
            <div className="border border-gray-500">
                <div className="mb-4">
                    <label className="w-full font-bold text-2xl">
                        イベント名
                    </label>
                    <label className="w-full font-bold text-2xl">
                        {targetName}
                    </label>
                </div>
                <div>
                    <label className="w-full font-bold text-2xl">
                        イベント詳細
                    </label>
                    <label>
                        {targetDescription}
                    </label>
                </div>
            </div>
            <form onSubmit={deleteEvent}>
                <input type="hidden" name="eventId" defaultValue={targetId || null}/>
                <div className="flex items-center justify-center mt-8 mb-8">
                    <button className="bg-rose-500 text-white font-bold px-4 py-4 rounded-md mr-4">
                        削除する
                    </button>
                    <button type="button" className="bg-blue-500 text-white font-bold px-4 py-4 rounded-md" onClick={backToDashboard}>
                        戻る
                    </button>
                </div>
            </form>
        </div>
    )
}
```


# サーバ(Laravel)側のコード
つづいてLaravel側です。

## ルーティング設定

```backend/Routes/api.php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RegisterController;//2025.01.08追加
use App\Http\Controllers\EventController;//2025.01.10追加

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider and all of them will
| be assigned to the "api" middleware group. Make something great!
|
*/

Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('/login',[RegisterController::class,'login']);//2025.01.08追加
Route::post('/eventRegistration',[EventController::class, 'registerEvent']);//2025.01.10追加
Route::post('/eventEdition',[EventController::class, 'editEvent']);
Route::get('/eventAllRead',[EventController::class, 'readAllEvents']);
Route::post('/eventDelete',[EventController::class, 'deletEvent']);

```

さいごに、削除する処理を追加します↓

```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);
        */
    }

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

        $event->where('id',$targetId)->delete();
        return response()->json(
            [
                'message'=>'削除完了'
            ],200);
    }

}


```

以上です。

# 関連記事はこちら↓

https://qiita.com/SKY-HaYaTo/items/9feefa587cfb6b922e03