ロゴ画像

  • HOME
  • Blog
    Allcategories
  • Portfolio
  • Profile
  • Contact

【React/TypeScript】単一画像のアップロード+プレビュー機能を実装する

フロントエンド
投稿日:
2023.11.05
最終更新日:
2023.11.05

はじめに

今回は、React + TypeScriptを使って画像をアップロードする機能の実装方法について、備忘録を兼ねて解説していきたいと思います。

なお、今回は添付した画像データを送信するところまでは解説していません。 フォームでのデータ送信まで含めた実装方法を知りたい方は以下の記事をご参照ください。

【React/TypeScript】単一画像をReact Hook Formを使って送信するの画像【React/TypeScript】単一画像をReact Hook Formを使って送信するエンジニアとして日頃の気づきやアウトプットを投稿しているブログです。koutaroinoue-log.com

画像を添付するInputを作成する

まずは画像を添付するための<input /> 要素を用意していきます。

<input /> 要素で直接実装しても良いですが、可読性の観点で今回は<input /> のラッパーコンポーネントを用意しています。

tsx
// InputImage/index.tsx

import React, { InputHTMLAttributes, forwardRef } from "react";

export type Props = {
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
  id: InputHTMLAttributes<HTMLInputElement>["id"];
};

const InputImage = forwardRef<HTMLInputElement, Props>(
  ({ onChange, id }, ref) => {
    return (
      <input
        ref={ref}
        id={id}
        type="file"
        accept="image/*"
        onChange={onChange}
        hidden
      />
    );
  }
);

export default InputImage;

input要素のそれぞれのプロパティの解説以下の通りです。

  • ref: 要素の値を参照する。
  • hidden: input要素を非表示にする。
  • type: file を指定することで任意のファイルを割り当てることができる。
  • accept: どのファイル種別を受け入れるかを記述する。
  • id: 後述の<label />要素のhtmlFor属性と紐づく。

また、今回はファイル添付用する際のボタンを独自に実装していくので本<input />要素自体はhidden で隠しておきます。

デフォルトでは以下のような見た目のボタンとなります。

react-single-file-upload1.png

画像アップロード用のフィールドを作成する

先程作成したInputImage を使用して、画像アップロード用のフィールドUIを作成していきます。

フィールドに加えて、この後実装するアップロードのキャンセルボタンも含めて作成しています。

tsx
// App.tsx

import { useRef } from "react";
import "./App.css";
import InputImage from "./InputImage";

const IMAGE_ID = "imageId";
const FIELD_SIZE = 210;

function App() {
  const fileInputRef = useRef<HTMLInputElement>(null);
  return (
    <>
      <label
        htmlFor={IMAGE_ID}
        style={{
          border: "white 3px dotted",
          width: FIELD_SIZE,
          height: FIELD_SIZE,
          display: "flex",
          borderRadius: 12,
          justifyContent: "center",
          alignItems: "center",
          overflow: "hidden",
          cursor: "pointer",
        }}
      >
        画像をアップロード
        {/* ダミーインプット: 見えない */}
        <InputImage ref={fileInputRef} id={IMAGE_ID} onChange={() => {}} />
      </label>

      <div style={{ height: 20 }} />
      {/* キャンセルボタン */}
      <button onClick={() => {}}>キャンセル</button>
    </>
  );
}

export default App;

ひとまず、フィールドを作成しただけですので、まだアップロードはできませんが以下のようなUIになります。

react-single-file-upload2.png

そして、「+ 画像アップロード」をクリックすると、OS標準のファイル選択のウインドウが起動するかと思います。

ファイルをアップロードできるようにする。

続いて、ファイルをアップロードできるようにしていきます。

画像アップロードを行うhandleFileChangeという関数を作成します。

処理内容としては、添付されたファイルをchangeイベントで取得し、stateとして状態管理しています。(それをコンソールに出力)

tsx
// App.tsx

import { useRef, useState } from "react";
import "./App.css";
import InputImage from "./InputImage";
import React from "react";

const IMAGE_ID = "imageId";
const FIELD_SIZE = 210;

function App() {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [imageFile, setImageFile] = useState<File | null>(null);

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.currentTarget?.files && e.currentTarget.files[0]) {
      const targetFile = e.currentTarget.files[0];
      setImageFile(targetFile);
    }
  };

  console.log(imageFile);

  return (
    <>
      <label
        htmlFor={IMAGE_ID}
        style={{
          border: "white 3px dotted",
          width: FIELD_SIZE,
          height: FIELD_SIZE,
          display: "flex",
          borderRadius: 12,
          justifyContent: "center",
          alignItems: "center",
          overflow: "hidden",
          cursor: "pointer",
        }}
      >
        画像をアップロード
        {/* ダミーインプット: 見えない */}
        <InputImage
          ref={fileInputRef}
          id={IMAGE_ID}
          onChange={handleFileChange}
        />
      </label>

      <div style={{ height: 20 }} />
      {/* キャンセルボタン */}
      <button onClick={() => {}}>キャンセル</button>
    </>
  );
}

export default App;

画像をアップロードすると、inputにファイルが添付されていることがわかります。

react-single-file-upload3.png

アップロード画像をプレビューする

続いて、添付した画像をプレビューする機能を作成していきます。

基本的な構造・流れは以下の通りです。

  • ファイルのアップロードされる。(先述)
  • state (imageFile)が更新されたら、画像URLを作成する。
  • 画像URLがあれば(=ファイルが添付されていたら)、画像を表示し、なければ「+ 画像をアップロード」を表示する。
tsx
// App.tsx

import { useRef, useState } from "react";
import "./App.css";
import InputImage from "./InputImage";
import { useGetImageUrl } from "./useGetImageUrl";
import React from "react";

const IMAGE_ID = "imageId";
const FIELD_SIZE = 210;

function App() {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [imageFile, setImageFile] = useState<File | null>(null);

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.currentTarget?.files && e.currentTarget.files[0]) {
      const targetFile = e.currentTarget.files[0];
      setImageFile(targetFile);
    }
  };

  // state (imageFile)が更新されたら、画像URLを作成する。
	const { imageUrl } = useGetImageUrl({ file: imageFile });

  return (
    <>
      <label
        htmlFor={IMAGE_ID}
        style={{
          border: "white 3px dotted",
          width: FIELD_SIZE,
          height: FIELD_SIZE,
          display: "flex",
          borderRadius: 12,
          justifyContent: "center",
          alignItems: "center",
          overflow: "hidden",
          cursor: "pointer",
        }}
      >
        {imageUrl && imageFile ? (
          <img
            src={imageUrl}
            alt="アップロード画像"
            style={{ objectFit: "cover", width: "100%", height: "100%" }}
          />
        ) : (
          "+ 画像をアップロード"
        )}
        {/* ダミーインプット: 見えない */}
        <InputImage
          ref={fileInputRef}
          id={IMAGE_ID}
          onChange={handleFileChange}
        />
      </label>

      <div style={{ height: 20 }} />
      {/* キャンセルボタン */}
      <button onClick={() => {}}>キャンセル</button>
    </>
  );
}

export default App;
tsx
// useGetImageUrl.ts

import { useEffect, useState } from 'react';

type Args = {
  file: File | null;
};

export const useGetImageUrl = ({ file }: Args) => {
  const [imageUrl, setImageUrl] = useState('');

  useEffect(() => {
    if (!file) {
      return;
    }

    let reader: FileReader | null = new FileReader();
    reader.onloadend = () => {
			// base64のimageUrlを生成する。
      const base64 = reader && reader.result;
      if (base64 && typeof base64 === 'string') {
        setImageUrl(base64);
      }
    };
    reader.readAsDataURL(file);

    return () => {
      reader = null;
    };
  }, [file]);

  return {
    imageUrl,
  };
};

画像URLを生成するロジック部分に関しては、useGetImageUrl.ts という名前でカスタムHookとして切り出しています。

ここまでで、画像アップロード+プレビューの実装が完成しました。

UIを確認しましょう。

react-singlr-file-upload4.png

画像アップロードのキャンセル機能を実装する

最後の、アップロードした画像をキャンセルする実装を行なって完成させましょう。

handleClickCancelButton という名前の関数名を作成し、キャンセルボタンが押されたらstateとして管理しているimageFileをnullとした上で、inputからも値を削除してあげるだけでOKです。

tsx
import { useRef, useState } from "react";
import "./App.css";
import InputImage from "./InputImage";
import { useGetImageUrl } from "./useGetImageUrl";
import React from "react";

const IMAGE_ID = "imageId";
const FIELD_SIZE = 210;

function App() {
  const fileInputRef = useRef<HTMLInputElement>(null);
  const [imageFile, setImageFile] = useState<File | null>(null);

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.currentTarget?.files && e.currentTarget.files[0]) {
      const targetFile = e.currentTarget.files[0];
      setImageFile(targetFile);
    }
  };

  const handleClickCancelButton = () => {
    setImageFile(null);
    // <input />タグの値をリセット
    if (fileInputRef.current) {
      fileInputRef.current.value = "";
    }
  };

  const { imageUrl } = useGetImageUrl({ file: imageFile });

  return (
    <>
      <label
        htmlFor={IMAGE_ID}
        style={{
          border: "white 3px dotted",
          width: FIELD_SIZE,
          height: FIELD_SIZE,
          display: "flex",
          borderRadius: 12,
          justifyContent: "center",
          alignItems: "center",
          overflow: "hidden",
          cursor: "pointer",
        }}
      >
        {imageUrl && imageFile ? (
          <img
            src={imageUrl}
            alt="アップロード画像"
            style={{ objectFit: "cover", width: "100%", height: "100%" }}
          />
        ) : (
          "+ 画像をアップロード"
        )}
        {/* ダミーインプット: 見えない */}
        <InputImage
          ref={fileInputRef}
          id={IMAGE_ID}
          onChange={handleFileChange}
        />
      </label>

      <div style={{ height: 20 }} />
      {/* キャンセルボタン */}
      <button onClick={handleClickCancelButton}>キャンセル</button>
    </>
  );
}

export default App;

おわりに

ファイルアップロード機能+プレビュー機能の実装は以上になります!

今回は画像をアップロードするところまでの解説でしたが、発展的な内容として、React Hook Formを使って添付した画像データを送信するところまでの実装方法についても別途解説していますので、気になった方はこちらの記事も併せてチェックしてみてください。

また、今回作成したソースコードを個人の公開リポジトリにアップしていますので、そちらもご利用ください。

GitHub - koutaro0205/react-single-image-upload: ブログ記事用の参考ソースコードの画像GitHub - koutaro0205/react-single-image-upload: ブログ記事用の参考ソースコードブログ記事用の参考ソースコード. Contribute to koutaro0205/react-single-image-upload development by creating an accogithub.com

最後まで読んでいただきありがとうございました。

See You !!

記事をシェアする
関連記事

サムネイル画像

フロントエンド

【React/TypeScript】react-markdownでマークダウン文字列から目次(TableOfContents)を実装する

2023.07.20

サムネイル画像

フロントエンド

Next.js + Storybook(Vite)環境にCSS in JSライブラリLinariaを導入する

2023.10.15

サムネイル画像

フロントエンド

【React/TypeScript】単一画像をReact Hook Formを使って送信する

2023.11.05

ロゴ画像

  • HOME
    • ホームへ戻る
  • Blog
  • Profile
  • Portfolio
プライバシーポリシー利用規約お問い合わせ
Copyright © Mates for engineer All Rights Reserved