【Reactドラッグ&ドロップでファイルのアップロード】react-dropzoneの使い方

2024/05/21に公開

はじめに

inputタグのtype属性を'file'に設定することでファイルのアップロードが可能ですが、UIをよりわかりやすくするために、以下のようにドラッグ&ドロップ機能を用いてファイルをアップロードできるようにします。

react-dropzone

react-dropzoneは、Reactのコンポーネントライブラリでありドラッグ&ドロップインターフェースを簡単に実装できるライブラリです。
https://react-dropzone.js.org/

インストールしてください。

npm install react-dropzone

ファイルアップロード用のコンポーネント作成

componentsフォルダにファイルアップロード用のコンポーネントを作成します。
今回は、DropzoneComponent.tsxという名前でファイルを作成します。

mkdir -p src/app/components && touch src/app/components/DropzoneComponent.tsx

下記の内容を記述してください。

src/app/components/DropzoneComponent.tsx
import { CSSProperties, useCallback, useState } from 'react';
import { useDropzone } from 'react-dropzone';

const styles: CSSProperties = {
  border: '2px dashed #cccccc',
  borderRadius: '5px',
  padding: '20px',
  textAlign: 'center',
  cursor: 'pointer',
};

export function Dropzone = () => {
  const [file, setFile] = useState<File | null>(null);

  const onDrop = useCallback((acceptedFiles: File[]) => {
    setFile(acceptedFiles[0]);
    console.log(acceptedFiles);
  }, []);

  const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });

  return (
    <div {...getRootProps()} style={styles}>
      <input {...getInputProps()} />
      {isDragActive && <p>ここにファイルをドロップしてください。</p>}

      {!file && !isDragActive && (
        <p>
          ファイルをドラッグ&ドロップするか、ここをクリックしてファイルを選択してください。
        </p>
      )}

      {file && (
        <div>
          <p>アップロードされたファイル名 </p>
          <p>{file.name} </p>
        </div>
      )}
    </div>
  );
};

onDrop

ファイルドロップエリアにファイルがドロップされた際に呼び出される非同期関数

FormData

https://developer.mozilla.org/ja/docs/Web/API/FormData

onDragEnter

ドロップゾーンにファイルがドラッグされて入った時に発生します。

onDragOver

ファイルがドロップゾーン上にある間、継続的に発生します。

onDragLeave

ファイルがドロップゾーンから出た時に発生します。

オプション

const { getRootProps, getInputProps, isDragActive } = useDropzone({
    onDrop,
    noClick: true,
    noKeyboard: true,
    accept: {
      'image/png': ['.png'],
      'image/jpeg': ['.jpeg'],
      'image/gif': ['.gif'],
    },
  });

noClick

trueに設定されている場合、ドロップゾーンをクリックしてファイル選択ダイアログを開くことはできません。つまり、ユーザーはドラッグアンドドロップのみを使用してファイルをアップロードできるようになります。

noKeyboard

trueに設定されている場合、キーボードを使ったインタラクション(例えば、エンターキーを押してファイル選択ダイアログを開く)が無効になります。

accept

どのファイルタイプを受け付けるかを指定します。
https://react-dropzone.js.org/#section-accepting-specific-file-types

ページコンポーネントでの使用

<DropzoneComponent />を追記してください。

isDragActive

isDragActiveは、ドラッグ中のアイテムがドロップゾーンの範囲内にある場合にtrueになります。

Types of property 'textAlign' are incompatible.

https://github.com/typestyle/typestyle/issues/281

下記の場合、style={styles}

const styles = {
  border: '2px dashed #cccccc',
  borderRadius: '5px',
  padding: '20px',
  textAlign: 'center',
  cursor: 'pointer',
};

次のようなエラーが出ます。

これは、stylesオブジェクトの各プロパティの型は自動的に推論され、特定の文字列リテラル型ではなく、string型として扱われることが多いです。これが原因で、Reactのstyleプロパティが期待する特定の文字列リテラル型と一致せず、エラーが発生することがあります。

Type '{ border: string; borderRadius: string; padding: string; textAlign: string; cursor: string; }' is not assignable to type 'Properties<string | number, string & {}>'.
  Types of property 'textAlign' are incompatible.
    Type 'string' is not assignable to type 'TextAlign | undefined'.ts(2322)
index.d.ts(2908, 9): The expected type comes from property 'style' which is declared here on type 'DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>'

解決するには、textAlign: 'center'textAlign: 'center' as constに変更、
またはCSSProperties型を明示的に指定して、textAlignの値として'center'が適切であることを保証してください。

const styles: CSSProperties = {
  border: '2px dashed #cccccc',
  borderRadius: '5px',
  padding: '20px',
  textAlign: 'center',
  cursor: 'pointer',
};

as const

as constは、TypeScriptにリテラル型を使うように指示します。
リテラル型は、値が特定の値であることを示す型です。つまり、'center' as constとすることで、その値が厳密に'center'であると型付けされます。
https://zenn.dev/nenenemo/articles/dd4417b5c99080#as-const:constアサーション

終わりに

何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉

Discussion