🍣

Inertia.jsで、PUTするとき、ファイルが送れない

2024/03/19に公開3

やりたいこと

PUTメソッドで、ファイルを送って、それをバックエンドで保存したい。
laravel側のrouter

Route::put('settings', [SettingController::class, 'update'])->name('setting.update');

発生した問題

react側

const { data, setData, post, put, errors, processing, setError }= useForm<User>({...auth.user});

const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (processing) return;

    put(route("setting.update"), {
        onSuccess: () => {  
            console.log("変更を保存しました。");
        },
    });
};

これで、putすると、データを送っていても、dataを送っているのに、
Laravel側のValidationに引っかかって、データがないと怒られる。

原因

https://inertiajs.com/file-uploads

公式ドキュメントにて、

Multipart limitations
Uploading files using a multipart/form-data request is not natively supported in some server-side frameworks when using the PUT,PATCH, or DELETE HTTP methods. The simplest workaround for this limitation is to simply upload files using a POST request instead.

However, some frameworks, such as Laravel and Rails, support form method spoofing, which allows you to upload the files using POST, but have the framework handle the request as a PUT or PATCH request. This is done by including a _method attribute in the data of your request.

という記載を発見。

要するに、PUTやDELETEメソッドでは、ファイルが送れないらしい。
でも、LaravelやRailsでは、method spoofingなるものを使って、POSTで送信しながらも、PUTとしてLaravel側で受け取ってもらう方法がある模様。
そのためには、
「_method:put」を、requestのデータに含めて送ればいいらしい。

対応

const { data, setData, post, put, errors, processing, setError }= useForm<User & {
    _method: "put";
}>({...auth.user,_method: "put",});

const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (processing) return;

    post(route("setting.update"), {
        onSuccess: () => {  
            console.log("変更を保存しました。");
        },
    });
};

のように変えればOK。
これで、putのフリをしながら、postで送って、ファイルが送れるようになる。

変更点①

const { data, setData, post, put, errors, processing, setError }= useForm<User & {
    _method: "put";
}>({...auth.user,_method: "put",});

で、dataに、_method:"put"を渡している。

変更点②

post(route("setting.update"), {
        onSuccess: () => {  
            console.log("変更を保存しました。");
        },
    });

putではなく、postで送っている。
なお、Laravel側でroute("setting.update")は、変わらずputで定義している。

Discussion

AKIHIAKIHI

質問です。
CSVファイルをローカルからインポートしてMySQLに保存する機能を作成したいと考えているのですが、こちらの記事と同じような流れでいけますでしょうか?
もしよろしければ、Controllerなどがどうなっているのかも知りたいです。
ReactはJSXです。
よろしくお願いいたします。

りっとんりっとん

CSVファイルをローカルからインポートしてMySQLに保存する機能の実装方法については、この記事の趣旨とは無関係になりますね。
ファイルをアップロードする点は同じですが、PUTである必要はなく、POSTでいいので、こちらの記事の内容とは異なります。

ただ、自分の経験が参考になれば、と思うので、
こんな感じで実装できるんじゃない?という自分の予想と、使えそうなライブラリ、参考記事を記載しておきます。あとはいろいろ調べて頑張ってください。

CSVファイルをローカルからインポートしてMySQLに保存

を実装するようであれば、
①React側でファイルを受け取り、バックエンドに送信。
本記事同様にInertia.jsも使っているようであれば、
例えばですが、下記のようにして、dataを用意し、
inputタグで、fileを受け取り、data.fileにそのfileを格納し、submit()すればいいと思います。
(Inertia.jsを使っていないなら、ReactのuseStateでdataを管理して、axiosでPOSTすればいいと思います)

const { data, setData, post }= useForm({
        file => null
    });

const submit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    post(route("import"));
};
<form onSubmit={submit}>
    <input type="file" onChange={(e) => setData("file",e.target.files[0]) />
    <button type="submit">インポート</button>
</form>

②バックエンドでの処理
routeを用意した上で、
ControllerでCSVを読み込みDBに保存する処理を行う。

public function store(Request $request){
    ~~~CSVを読み込み、DBの保存する処理~~~
}

CSVを読み込み、DBに保存する処理は、自分もパッとコードが書けるわけでもないので、ご自身で調べてください。
CSVの読み取りはLaravel Excelというライブラリを使うのが比較的簡単かと思います。

具体的な実装は下記の方の記事が参考になると思います。
https://kodyblog.com/laravel-excel/
https://reffect.co.jp/laravel/laravel_excel_master

おそらく、上記のライブラリを使っても、一筋縄ではいかないと思いますが、
いろいろ試行錯誤しながら頑張ってみてください。

AKIHIAKIHI

すごくご丁寧にありがとうございます!!
頑張って形にしてみせます!