九保すこひ@フリーランスエンジニア|累計300万PVのブログ運営中
さてさて、これは実際に私が使ってみて「うーん…」となった話なのですが、少し大きなファイルをアップロードする際に次のようになってしまいました。
ファイル以外のデータが間違っていて、また時間をかけてアップロードしなくちゃいけなくなった…😩
つまり、これは以下の流れになってしまい、また長い待ち時間が発生することになってしまった訳です…
- データ&ファイル送信
- データがバリデーションに引っかかった
- 入力を直してまた送信(=時間がかかる)
さすがにこれはユーザビリティとして良いことではないので、今回「2段階バリデーション」なるものを作ってみることにしました。
つまり、送信が早い「ファイル以外」の入力データだけ先にバリデーションをかけ、問題がなければファイルも合わせて送信するというものです。
※ クライアントサイドでバリデーションすればいいとも思うのですが、バリデーションは結局サーバーサイドも用意しないといけないので、どうせだったら一元管理したいわけです。
そこで❗
今回はLaravel + React
で2段階バリデーションなるものを実装してみることにしました。
ぜひ何かの参考になりましたら嬉しいです。😊✨
「ビアバーでとなりにいた
海外の方と話してみたら、
パイロットさんで驚きました」
開発環境: Laravel 9.x、React、Inertia.js、Vite
コントローラーをつくる
では、まずはコントローラーをつくっていきます。
php artisan make:controller TwoStepsValidationController
すると、ファイルが作成されるので中身を以下のように変更します。
app/Http/Controllers/TwoStepsValidationController.php
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Inertia\Inertia; class TwoStepsValidationController extends Controller { public function create() { return Inertia::render('TwoStepsValidation/Create'); } public function preStore(Request $request) { $request->validate([ // 事前バリデーション 'title' => 'required', 'description' => 'required', ]); return redirect()->route('two_steps_validation.create'); } public function store(Request $request) { $request->validate([ // 本バリデーション 'title' => 'required', 'description' => 'required', 'file' => ['required', 'file', 'max:2048'], ]); // ここで保存処理を行う return redirect()->route('two_steps_validation.create'); } }
この中でやっているのは、それぞれ以下のとおりです。
- create() ・・・ 送信フォームの表示
- preStore() ・・・ 事前バリデーション
- store ・・・ 本バリデーション(&保存)
ビューをつくる
では、続いて先ほどcreate()
内でセットしたビュー(テンプレート)を作成します。
resources/js/Pages/TwoStepsValidation/Create.jsx
import React, {useState, useRef} from 'react'; import {Inertia} from "@inertiajs/inertia"; export default function Create(props) { // Data const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [file, setFile] = useState(null); const errors = props.errors; // Refs const inputFileRef = useRef(); // Handlers const handleFileChange = e => { setFile(e.target.files[0]); }; const handlePreSubmit = () => { const url = route('two_steps_validation.pre_store'); const data = { // ファイル以外のデータ送信 title, description, }; Inertia.post(url, data, { onSuccess() { handleSubmit(); // 本送信 } }); }; const handleSubmit = () => { const url = route('two_steps_validation.store'); const data = { // 全データ送信 title, description, file, }; Inertia.post(url, data, { forceFormData: true, // 必ず FormData で送信 onSuccess() { setTitle(''); setDescription(''); setFile(null); inputFileRef.current.value = null; // ファイル選択をクリア alert('完了しました'); } }); }; return ( <div className="w-48 p-5"> <div className="mb-6"> <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"> タイトル </label> <input type="text" value={title} className="border border-gray-300 text-gray-900 text-sm" onChange={e => setTitle(e.target.value)} /> {errors.title && <p className="text-red-500 text-xs italic">{errors.title}</p>} </div> <div className="mb-6"> <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"> 内容 </label> <textarea value={description} className="border border-gray-300 text-gray-900 text-sm" onChange={e => setDescription(e.target.value)} /> {errors.description && <p className="text-red-500 text-xs italic">{errors.description}</p>} </div> <div className="mb-6"> <label className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"> 容量が大きいファイル </label> <input ref={inputFileRef} type="file" onChange={e => handleFileChange(e)} /> {errors.file && <p className="text-red-500 text-xs italic">{errors.file}</p>} </div> <button type="button" className="text-white bg-blue-700 hover:bg-blue-800 font-medium rounded-lg text-sm px-4 py-2.5 text-center" onClick={() => handlePreSubmit()}> 送信する </button> </div> ); }
この中で通常の重要なのが、「ファイル選択」の部分です。
というのも、ファイル選択は通常のデータバインディングとは違い、以下の作業を自分で実行する必要があるためです。
- 選択されたらファイルを取得&データ格納する
- 送信が完了したら、選択済みファイルをクリアする
なお、ファイル選択のクリアは直接<input>
要素にアクセスできるようuseRef()
をセットしていますが、inputFileRef.current.value = null;
と、current
が必要なことに注意してください。
ルートをつくる
では、最後にルートを追加します。
routes/web.php
// 省略 use App\Http\Controllers\TwoStepsValidationController; // 省略 Route::prefix('two_steps_validation')->controller(TwoStepsValidationController::class)->group(function(){ Route::get('/create', 'create')->name('two_steps_validation.create'); Route::post('/pre', 'preStore')->name('two_steps_validation.pre_store'); Route::post('/', 'store')->name('two_steps_validation.store'); });
これで作業は完了です。
お疲れ様でした✨😊👍
テストしてみる
では、実際にテストしてみましょう❗
Vite
を起動後、ブラウザで、「http://******/two_steps_validation/create」へアクセスします。
すると、以下のようなフォームが表示されるので、まずは何も入力せず「送信する」ボタンをクリックしてみましょう。
すると・・・・・・
はい❗
2段階でのバリデーションですので、まずは「タイトル」と「内容」の2つだけにエラーが表示されました。
では、次に「タイトル」と「内容」は入力しますが、ファイルは空のままで送信してみましょう。
どうなるでしょうか・・・・・・
はい❗
先ほどはバリデーション・チェックされなかったファイルにエラーが表示されました。
成功ですね😊✨
では、折角ですので、ファイルを選択して送信してみましょう。
うまくいくでしょうか・・・・・・???
はい❗
完了を知らせるアラートが表示されて、さらにフォームが初期状態に戻りました。
すべて成功です😊✨
企業様へのご提案
今回のようにサイズの大きなファイルを送信する場合は、バリデーションを分割することでユーザビリティを向上させることができます。
このような「ちょっとした配慮」をウェブサイトやシステムに組み込むことでより使いやすくなり、作業効率や満足度をあげることができます。
もしそういった改善をご希望でしたら、お問い合わせからお気軽にご連絡ください。
お待ちしております。m(_ _)m
おわりに
ということで、今回はLaravel + React
を使った「2段階バリデーション」を実装してみました。
この記事で直近のReact
記事は4つめですが、ref
などVue
と共通する部分が多いので、結構すんなりと実装できホッとしています(正直React
はマシでしたが、その昔のAngular
の苦手意識が蘇ってきたんですよね…😂)
やはり設計思想は違えど、向かっている方向は同じということなのでしょうか。
今後も開発の世界から目が離せませんね。
ではでは〜❗
「この前、国産ビール発祥の地
(北新地)に行ってきました🍺」