【Laravel】React を使ったプレビュー画像入力をつくる

こんにちは。フリーランス・コンサルタント&エンジニアの 九保すこひ です。

さてさて、前回&前々回からViteを使ったReactの記事をお届けしましたが、いかがだったでしょうか。

個人的には長らくVueを使っていたので、まだまだ効率化できていないコードがあるかもですが、ぜひ同じような学習者さんたちに楽しんでいただけたら嬉しいです。

そして、今回ですがReactを使ったらやってみたいと考えていたある機能にすることにしました。

それは・・・・・・

画像のプレビュー機能

です。

つまり、以下のようなものですね。

  1. 画像を選択する
  2. その画像をページに表示(画像があっているか確認できる)
  3. 選択した画像は上書き&削除(キャンセル)することもできる

ということで、今回はReactでプレビュー画像入力をつくってみたいと思います。

ぜひ何かの参考になりましたら嬉しいです。😊✨

「滋賀県の日野町、
いいところでした😊」

開発環境: Laravel 9.x、Vite、InertiaJS、React

前提として

LaravelViteを使って開発ができることが前提です。
もしまだの方は以下の記事を参考にしてViteが動くところまで準備しておいてください。

📝 Vite + Inertia + React でログイン機能をインストールする 

※ なお、ログイン機能は不要です。

ルートをつくる

では、まずはLaravelに画像プレビュー用のページをルートにつくりましょう。

routes/web.php

// 省略

use Inertia\Inertia;

// 省略

Route::get('preview_image_input', fn() => Inertia::render('PreviewImageInput/Index'));

この中のrender()にセットしているのが、これからresources/Pages内につくることになる「ビュー」になります。

ビューをつくる

では、ルートでセットしたビューをつくっていきます。

resources/js/Pages/PreviewImageInput/Index.jsx

import React from "react";
import PreviewImageInput from "@/Components/PreviewImageInput";

export default function Index() {

    const handleImageChange = e => { // 画像に変化があったら実行される

        console.log(e);

    };

    return (
        <div className="p-4">
            <h1 className="font-bold mb-3">&#x1F4DD; React を使ったプレビューつき画像入力のサンプル</h1>
            <PreviewImageInput
                onImageChange={e => handleImageChange(e)}
            />
        </div>
    );
}

とてもシンプルな内容ですが、PreviewImageInputは次でつくるコンポーネントになります。

コンポーネントをつくる

では、今回のメインになります。
ビュー内でセットしたコンポーネントをつくっていきましょう。

resources/js/Components/PreviewImageInput.jsx

import React, { useState } from 'react';

export default function PreviewImageInput(props) {

    // Data
    const label = props.label || '画像を選択してください';
    const labelClassName = props.labelClassName || 'bg-gray-200 text-gray-700 text-sm font-bold py-2 px-4 rounded';
    const imageClassName = props.imageClassName || 'w-48';
    const accept = props.accept || 'image/jpeg,image/png';
    const [imageData, setImageData] = useState(null);

    // Methods
    const handleFileChange = e => { // 画像が選択された

        const files = e.target.files;

        if (files.length > 0) {

            const file = files[0];
            const reader = new FileReader();
            reader.onload = e => {

                const imageData = e.target.result;
                setImageData(imageData);
                handleImageChange({
                    status: 'added',
                    file: file,
                    imageData: imageData
                });

            }
            reader.readAsDataURL(file);

        }

    };
    const handleImageDelete = () => { // 画像の削除

        if(confirm('画像を削除します。よろしいですか?')) {

            setImageData(null);
            handleImageChange({
                status: 'removed',
                file: null,
                imageData: null
            });

        }

    };
    const handleImageChange = e => { // 画像に変化があったとき

        if(typeof props.onImageChange === 'function') { // 呼び出し側でイベントがセットされていれば実行

            props.onImageChange(e);

        }

    };

    return (
        <>
            <label className={labelClassName}>
                {label}
                <input
                    type="file"
                    accept={accept}
                    style={{ display: 'none' }}
                    onChange={e => handleFileChange(e)}
                />
            </label>
            {imageData &&
                <div className="mt-5">
                    <img src={imageData} className={imageClassName} />
                    <button
                        type="button"
                        className="bg-red-500 text-gray-100 text-sm font-bold py-1 px-3 rounded mt-3"
                        onClick={e => handleImageDelete(e)}>削除</button>
                </div>
            }
        </>
    );

}

基本的にはよく用いられる、以下の流れをReactで実装しているだけです。

  1. 画像を選択
  2. JavaScript 画像を読み込む
  3. 画像データを <img> タグへセット

なお、重要なのが以下の部分です。

props.onImageChange(e);

これは、propsを通して親の要素のイベントを実行しているんですね。(ここはホントにシンプルでいいですね👍)

つまり、Index.jsxで言うと、以下の太字の部分が実行されるということになります。

resources/js/Pages/PreviewImageInput/Index.jsx

<PreviewImageInput
    onImageChange={e => handleImageChange(e)}
/>

そのため、onImageChangeがセットされていない場合にはエラーになってしまうので、以下のIF文でチェックをしています。

if(typeof props.onImageChange === 'function') {

     // イベントがセットされていれば実行

}

では、今回は短いですがこれで完了です。
お疲れ様でした。😊✨

企業様へのご提案

今回のViteを利用した開発では、これまで必要だった「ビルド(構築)待ち」がほぼなくなるため、高速な開発が実現できます。

例えば、毎回ビルドに10秒必要だったとすると6回ビルドするだけで60秒=1分無駄にしていることになります。もし100回ビルドするとなると1000秒=16分40秒です。

16分あったら、小さなコンポーネントなら十分つくれます。

そして、これが月単位となると… or 開発人数が多くなると…

そうです。
その分不必要な時間が必要になるわけです。

ということで、ぜひViteを使った構築をご希望でしたらお問い合わせからご相談ください。

お待ちしております。😄✨

デモページを作りました

📝 デモページ

テストしてみる

では、実際にテストしてみましょう。

まずは以下のコマンドでViteを起動して、ブラウザで「https://******/preview_image_input」へアクセスします。

npm run dev

すると、以下のような表示になるので、ボタンをクリックして「この前行った、滋賀県のビール醸造所の写真 🍺」を選択してみます。

すると・・・・・・

はい❗
うまくビールの写真が表示されました。

では、コンソールの方はどうなっているでしょうか。

はい❗

statusaddedになっていて、画像データやファイル本体も取得できています。

それでは、このままの状態で別の写真(この前見に行った珍ポスト)を選択してみましょう。

どうなったでしょうか・・・・・・

はい❗画像が切り替わりました。
こちらも成功です。

では、最後に「削除」ボタンをクリックして画像が消えるかもチェックしてみましょう。

クリックしてみます。

すると・・・・・・???

画像が削除されました。
では、コンソールはどうでしょうか。

今回はstatusremovedになっていて、画像&ファイルもnullになっています。

すべて成功です😊✨

開発のご依頼お待ちしております
開発のご依頼はこちらから: お問い合わせ
どうぞよろしくお願いいたします! by 九保すこひ

おわりに

ということで、今回はReactでプレビュー画像つきの入力ができるように実装してみました。

個人的な今回のハイライトは、props.XXXXXX()で親要素のイベントが実行できることを知った部分です。

Vueではemitを使っていましたが、少しクセが強い部分だったので今回のようなコードはよりシンプルで好感触でした。

ぜひ皆さんもいろいろとやってみてくださいね。

ではでは〜❗

「滋賀県のぽけフタ、
全件ゲットしました❗」

このエントリーをはてなブックマークに追加       follow us in feedly