UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【React】react-dropzoneでファイルアップロード機能を実装する

react-dropzoneというファイルアップロードに便利なライブラリを知ったので調べた

react-dropzone.js.org

react-dropzone は、ファイルのドラッグ&ドロップインターフェースをReactアプリケーションに簡単に追加するためのライブラリ

React Dropzoneの主な機能は以下

  • ファイルのドラッグアンドドロップ対応
  • クリックによるファイル選択
  • 一度に複数ファイルのサポート(アップロード可能なファイルの数やタイプを制限するオプションも提供されている)
  • ファイルタイプの制御
  • カスタマイズ可能なUI(ドロップゾーンの外観やインタラクションは柔軟にカスタマイズできる)
  • フックベースのAPI

実装例

import React from 'react';
import { useDropzone } from 'react-dropzone';

function MyDropzone() {
  const { getRootProps, getInputProps, acceptedFiles } = useDropzone({
    onDrop,  // ユーザーがファイルをドロップしたときに呼び出されるコールバック関数
    accept: 'image/*',  // どのファイルタイプを受け入れるかを制御
    multiple: true,
    disabled: false,
    noClick: true,
    noKeyboard: true,
  });

  const files = acceptedFiles.map(file => (
    <li key={file.path}>
      {file.path} - {file.size} bytes
    </li>
  ));

  return (
    <div {...getRootProps({ className: 'dropzone' })}>
      <input {...getInputProps()} />
      <p>Drag 'n' drop some files here, or click to select files</p>
      <ul>{files}</ul>
    </div>
  );
}

getRootProps をpropsとして渡されたコンポーネントがドロップゾーンになる

acceptはオブジェクトで、{ "image/jpeg": [".jpg", ".jpeg"] }のようにMIME-typeと拡張子をセットで指定もできる

カスタムバリデーション

独自のvalidationが定義できる

例えば、以下の精査条件を設けるとする

  • ファイルタイプ: 画像ファイル(JPEGPNG)のみを受け入れる
  • ファイルサイズ: 各ファイルのサイズが2MB以下であること
  • カスタムバリデーション: 画像の縦横比1:1であること

実装は以下のようになる

import React, { useCallback } from 'react';
import { useDropzone } from 'react-dropzone';

function MyDropzone() {
  const maxSize = 2 * 1024 * 1024; // 2MB

  const customValidator = (file) => {
    const errors = [];

    // ファイルサイズのチェック
    if (file.size > maxSize) {
      errors.push({
        code: "file-too-large",
        message: `ファイルサイズが2MBを超えています: ${file.name}`,
      });
    }

    // 画像の縦横比のチェック
    return new Promise((resolve) => {
      const img = new Image();
      img.src = URL.createObjectURL(file);
      img.onload = () => {
        const aspectRatio = img.width / img.height;
        if (aspectRatio < 0.5 || aspectRatio > 2) {
          errors.push({
            code: "invalid-aspect-ratio",
            message: `縦横比が許可されている範囲外です: ${file.name}`,
          });
        }
        resolve(errors.length ? errors : null);
      };
    });
  };

  const onDrop = useCallback((acceptedFiles, rejectedFiles) => {
    acceptedFiles.forEach((file) => {
      console.log('Accepted file:', file);
    });

    rejectedFiles.forEach((file) => {
      file.errors.forEach((err) => {
        console.error(`${file.file.name} - ${err.message}`);
      });
    });
  }, []);

  const { getRootProps, getInputProps } = useDropzone({
    accept: 'image/jpeg, image/png',
    validator: customValidator,
    onDrop,
  });

  return (
    <div {...getRootProps({ className: 'dropzone' })}>
      <input {...getInputProps()} />
      <p>ファイルをここにドラッグ&ドロップ、またはクリックして選択</p>
    </div>
  );
}

export default MyDropzone;