⚛️

Reactの関数コンポーネントとFormikを使用して画像をブラウザにアップロードする

2021/11/12に公開

概要

関数コンポーネントと Formik を使用して画像をブラウザにアップロードする方法についてまとめます。

ソースコードは以下
https://github.com/Msksgm/react-image-upload-example

codesandbox は以下
https://codesandbox.io/s/nervous-sound-tc0th

実装

全体像

実装の全体は以下のようになります(./src/App.tsx)。
3つのコンポーネント(ThumbImageUploaderApp)から構成されています。
順を追って説明していきます。

import { FC, useState, useEffect } from "react";
import { Formik } from "formik";
import * as yup from "yup";

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

const Thumb: FC<ThumbProps> = ({ file }) => {
  const [loading, setLoading] = useState<Boolean>(true);
  const [thumb, setThumb] = useState<string>();

  useEffect(() => {
    const reader = new FileReader();
    if (file) {
      reader.readAsDataURL(file);
      reader.onload = () => {
        setThumb(reader.result as string);
      };
      setLoading(false);
    }
  }, [file]);

  if (!file) {
    return null;
  }

  if (loading) {
    return <p>Loading....</p>;
  }

  return (
    <img
      src={thumb}
      alt={file.name}
      className="img-thumbnail mt-2"
      height={200}
      width={200}
    />
  );
};

const ImageUploader: FC = () => {
  return (
    <Formik
      initialValues={{ file: null }}
      onSubmit={(values: any) => {
        alert(
          JSON.stringify({
            fileName: values.file.name,
            type: values.file.type,
            size: `${values.file.size} bytes`,
          })
        );
      }}
      validationSchema={yup.object().shape({
        file: yup.mixed().required(),
      })}
    >
      {({ handleSubmit, setFieldValue, values }) => {
        return (
          <form onSubmit={handleSubmit}>
            <div className="form-group">
              <label htmlFor="file">File upload</label>
              <input
                type="file"
                id="file"
                name="file"
                onChange={(event) => {
                  setFieldValue(
                    "file",
                    event.currentTarget.files !== null
                      ? event.currentTarget.files[0]
                      : null
                  );
                }}
                className="form-control"
              />
              <Thumb file={values.file} />
            </div>
            <button type="submit" className="btn btn-primary">
              submit
            </button>
          </form>
        );
      }}
    </Formik>
  );
};

const App: FC = () => {
  return (
    <div className="container">
      <ImageUploader />
    </div>
  );
};

export default App;

コンポーネント

Thumb コンポーネント

Thumbコンポーネントは、<input type="file">から受け取った画像をサムネイルとして表示するコンポーネントです。
useEffectで file の変換を検知したときに、ファイルを読み込みsetThumbでサムネイルを設定します。

const Thumb: FC<ThumbProps> = ({ file }) => {
  // useStateで画像の読み込みとファイルの状態を設定
  const [loading, setLoading] = useState<Boolean>(true);
  const [thumb, setThumb] = useState<string>();

  // ファイルに変化があったときに、状態を更新する
  useEffect(() => {
    const reader = new FileReader();
    if (file) {
      reader.readAsDataURL(file);
      reader.onload = () => {
        setThumb(reader.result as string);
      };
      setLoading(false);
    }
  }, [file]);

  // ファイルがなければ空を返す
  if (!file) {
    return <></>;
  }

  // ローディング中は、以下のコンポーネントを返す
  if (loading) {
    return <p>Loading....</p>;
  }

  // ファイルがある + ローディング完了 のとき<img>を返す
  return (
    <img
      src={thumb}
      alt={file.name}
      className="img-thumbnail mt-2"
      height={200}
      width={200}
    />
  );
};

ImageUploader コンポーネント

ImageUploaderコンポーネントは、Fomikを使って画像アップロードする form のコンポーネントです。
Thumbコンポーネントの引数にvalues.fileで画像を渡しています。
onSubmitで画像の情報について表示するようにしています。この箇所を変更することで S3 などに画像を送信できます。

const ImageUploader: FC = () => {
  return (
    <Formik
      initialValues={{ file: null }}
      onSubmit={(values: any) => {
        alert(
          JSON.stringify({
            fileName: values.file.name,
            type: values.file.type,
            size: `${values.file.size} bytes`,
          })
        );
      }}
      validationSchema={yup.object().shape({
        file: yup.mixed().required(),
      })}
    >
      {({ setFieldValue, values }) => {
        return (
          <Form>
            <div className="form-group">
              <label htmlFor="file">File upload</label>
              <input
                type="file"
                id="file"
                name="file"
                onChange={(event) => {
                  setFieldValue(
                    "file",
                    event.currentTarget.files !== null
                      ? event.currentTarget.files[0]
                      : null
                  );
                }}
                className="form-control"
              />
              <Thumb file={values.file} />
            </div>
            <button type="submit" className="btn btn-primary">
              submit
            </button>
          </Form>
        );
      }}
    </Formik>
  );
};

App コンポーネント

Appコンポーネントです。ImageUploaderを表示するだけです。

const App: FC = () => {
  return (
    <div className="container">
      <ImageUploader />
    </div>
  );
};

動作確認

環境

  • node
    • v14.15.4
    • yarn, ts-node, @types/node グローバルインストール済み

下準備

git clone https://github.com/Msksgm/react-image-upload-example.git
cd react-image-upload-example
yarn

実行

yarn start

画面確認

「ファイルを選択」ボタンを押下して、画像を選ぶと以下のように表示されます。

参考

https://stackoverflow.com/questions/56512529/how-do-i-convert-reader-result-into-string

https://codesandbox.io/s/lkkjpr5r7?file=/index.js:352-358

https://formik.org/docs/tutorial

https://qiita.com/fufufukakaka/items/5d4a2f2272b8f1a4a16f

https://tech.zeals.co.jp/entry/2019/05/21/135544

Discussion