🕺

React Hook Formを使ってドラッグ & ドロップ可能な画像アップロード用フォームを実装する

2022/02/09に公開

要件

  • 画像プレビュー
  • 画像プレビューをクリックしてファイル選択ダイアログ表示
  • 画像プレビューにファイルをドロップで選択できる

つまづいた箇所

  • divをクリックした時に子要素であるinputのclickイベントを発火させたいが、useRefの設定とregisterの登録を同時に行おうとするとうまくいかない
  • divに対してファイルをドラッグ & ドロップしたときにinputのchangeイベントを発火させたいがうまくいかない

実装

cssや細かい箇所は所々省略している

import { useState, useRef } from "react";

const acceptImageFiles = ["image/png", "image/jpeg", "image/gif", "image/jpg"];
const loadImage = (files, inputFile, setImage) => {
  const file = files[0];
  const reader = new FileReader();
  reader.onload = (event) => {
    setImage(event.target.result);
  }
  reader.readAsDataURL(file);
};

export default function ImageUploadForm({imageSrc, alt, register}) {

  const [image, setImage] = useState(imageSrc);
  const inputFile = useRef(null);
  const {ref, onChange, ...aRegister} = register;

  return (
    <div
      onDragOver={(event) => event.preventDefault()}
      onDrop={(event) => {
        if (acceptImageFiles.includes(event.dataTransfer.files[0].type)) {
          inputFile.current.files = event.dataTransfer.files;
          inputFile.current.dispatchEvent(new Event("change", { bubbles: true }));
        }
        event.preventDefault();
      }}
      onDragLeave={() => console.log("ドラッグリーブ")}
      onClick={(event) => {
        inputFile.current.click();
        event.stopPropagation();
      }}
    >
      <img src={image} />
      <input
        type="file"
        accept={acceptImageFiles}
        onChange={(event) => {
          if (event.target.files.length > 0) {
            onChange(event);
            loadImage(event.target.files, inputFile, setImage)
          }
        }}
        ref={(element) => {
          ref(element);
          inputFile.current = element;
        }}
        {...aRegister}
      />
    </div>
  );
}

詳細説明

"divをクリックした時に子要素であるinputのclickイベントを発火させたいが、useRefの設定とregisterの登録を同時に行おうとするとうまくいかない"の解決方法

useRefを使ってinputを参照するための変数を用意する
react hook formのregisterをスプレッド構文で展開し、refを取りだす

const inputFile = useRef(null);
const {ref, onChange, ...aRegister} = register;

input要素のrefに対して、デフォルトのrefの実行に加えinputFileにinput要素を設定する実装を行う。
スプレッド構文で展開した残りのaRegiterを登録する

<input
  type="file"
  ref={(element) => {
    ref(element);
    inputFile.current = element;
  }}
  {...aRegister}
/>

divのonClickイベントでinputFile.current.click()を実行する

<div
  onClick={(event) => {
    inputFile.current.click();
    event.stopPropagation();
  }}
>

"divに対してファイルをドラッグ & ドロップしたときにinputのchangeイベントを発火させたいがうまくいかない"の解決方法

divのonDropイベントでuseRefで取得したinput要素のdispatchEventメソッドを使ってchangeイベントを発火させる

<div
  onDrop={(event) => {
    if (acceptImageFiles.includes(event.dataTransfer.files[0].type)) {
      inputFile.current.files = event.dataTransfer.files;
      inputFile.current.dispatchEvent(new Event("change", { bubbles: true }));
    }
    event.preventDefault();
  }}
>

あとがき

他によい方法があったらご教示ください

Discussion