[React] 汎用的に使えるファイルドロップ部品を作る

2022年11月15日火曜日

Javascirpt react

t f B! P L

enter image description here

昨今のWEBアプリでアップロードを行う場合、通常のファイル選択(<input type="file"/>)に加えて、エクスプローラからのドラッグ&ドロップに対応している事が多い。

というわけで本記事では、Reactで汎用的に使えるファイル選択&ドロップ部品を作るサンプルコードを紹介します。

スポンサーリンク

完成イメージ

enter image description here

上のように、通常のファイル選択と、点線の枠内にファイルをドロップできるコンポーネントとします。また、ファイルの単一選択 or 複数選択可をプロパティで指定可能にします。

サンプルコード

プロパティの定義

2つのプロパティを持ちます。

onChangeは、ファイルが選択された時に親コンポーネントに通知するためのイベントハンドラです。
multipleは、複数選択を許可するかどうかのフラグです。

/**
 * プロパティ
 */
interface FileDropzoneProp {
  /**
   * ファイル選択時のイベント
   */
  onChange: (files: FileList) => void,
  /**
   * 複数ファイル選択を許可するか
   */
  multiple?: boolean
}

コンポーネント本体

import React, { ChangeEvent, DragEvent, useCallback, useRef, useState } from 'react'
import style from './Style.module.css';


/**
 * ファイルドロップコンポーネント
 */
function FileDropzone({
  onChange,
  multiple
}: FileDropzoneProp) {
  const [dragging, setDragging] = useState(false)
  const refFileElement = useRef<HTMLInputElement>(null)

  /**
   * ドロップ時の処理
   */
  const handleDrop = useCallback(
    async (e: React.DragEvent<HTMLDivElement>) => {
      e.stopPropagation()
      e.preventDefault()

      if (e.dataTransfer.files?.length > 0 && refFileElement.current) {
        if (multiple) {
          refFileElement.current.files = e.dataTransfer.files
        } else {
          const dt = new DataTransfer()
          dt.items.add(e.dataTransfer.files[0])
          refFileElement.current.files = dt.files
        }
        onChange(refFileElement.current.files)
      }
      setDragging(false)
    }, 
  [onChange])

  /**
   * ファイル選択(input type="file"からの選択)
   */
  const handleChange = useCallback(
    async (e: ChangeEvent<HTMLInputElement>) => {
      if (e.target.files) {
        onChange(e.target.files)
      }
    },
  [onChange])

  /**
   * Dragover or Dragleave
   * @param e 
   */
  const handleDragoverOrLeave = useCallback((e: DragEvent<HTMLDivElement>) => {
    e.stopPropagation()
    e.preventDefault()
    setDragging(e.type === "dragover")
  }, []);

  return (
    <div
      className={`${style.dragzone} ${dragging ? style.dragging : ""}`}
      onDragOver={handleDragoverOrLeave}
      onDragLeave={handleDragoverOrLeave}
      onDrop={handleDrop}>
      ここにファイルをドロップ<br />
      または<br />
      <input 
        ref={refFileElement} 
        onChange={handleChange} 
        type="file" 
        multiple={multiple}/>
    </div>
  )
}

export default FileDropzone

CSS

念の為他のスタイルに影響が出ないように、CSSモジュールとして作る。
ファイル名は Style.module.css とする。

/** ドラッグエリア */
.dragzone {
  padding: 2rem;
  background: #eee;
  border: 5px dashed #999;
  color: #555;
  text-align: center;
}
  /** ドラッグ中の状態 */
  .dragging {
    background: #ddd;
  }

スポンサーリンク

使用方法

配置はインポートして置くだけ。
ファイルが選択されたときのイベントは onChangeで受け取り、イベントの引数に選択されたファイルが配列(FIleList 型)で渡される。

<FileDropzone onChange={files => {
  console.log(files[0])
}} />

複数選択を可能にする場合、multiにtrueを指定。

<FileDropzone multiple={true} onChange={files => {
  for( let i = 0; i < files.length; i++) {
    console.log(files[i])
  }
}} />

まとめ

Reactでファイル選択&ドロップできる、汎用的に使えるコンポーネントのサンプルコードをしました。

もちろん自作などしなくても、ちょっと検索すれば、ファイル選択関連で npmでインストールできるコンポーネントは沢山ヒットする。ただ、見た目や操作性をこだわりたいのであれば、自前で作るのもありだろう。

スポンサーリンク
スポンサーリンク

このブログを検索

Profile

自分の写真
Webアプリエンジニア。 日々新しい技術を追い求めてブログでアウトプットしています。
プロフィール画像は、猫村ゆゆこ様に書いてもらいました。

仕事募集もしていたり、していなかったり。

QooQ