🌝

ReactでFileのアップロードボタンをカスタマイズする

2021/03/10に公開
1

こんにちはハトです。色々の記事をあさっているうちに、ファイルアップロードのやり方の知見がたまったので共有します。

基本

以下のようにinputタグにfileを指定すれば、ファイルエクスプローラー(もしくはファインダー)が開きます。選択したファイルはonChangeコールバックの引数に渡されます。

<input type='file' onChange={onFileInputChange}/>

onChangeコールバックの引数ではe.target.files に選択したファイルが格納されています。

  const onFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.files);
  };

ちなみにaccept="image/*" 属性を渡すと画像のみ選択できるようになります。

<input type='file' accept="image/*" onChange={onFileInputChange}/>

アップロードボタンのカスタマイズ

上記「基本」セクションでファイルアップロードはできます。ただ標準のinputボタンはださいので、なんとかカスタマイズしたいところです。

やり方は、

  • 先程のinputタグにhidden属性を渡し、非表示にする。
  • useRefを利用して、inputタグの生domをrefに格納する。
  • 別にボタンを用意する。このボタンをクリックすると、先程のrefを用いて遠隔でinputタグをクリックする。

useRefに関してはこちらの記事もご参考ください。
https://zenn.dev/dove/articles/e2d962e9d69e20

[応用編] ファイルインプットをコンポーネント化する

先程の「アップロードボタンのカスタマイズ」セクションのやり方では、常にボタンとinputタグを同一のコンポーネントに収めなければいけません。それも良いと思うのですが、このセクションではinputタグ部分のみをコンポーネント化してみます。

やり方は、

  • inputタグをHiddenInputコンポーネントに外だしする。
  • refのコンポーネント間での受け渡しが発生するため、HiddenInputコンポーネントをforwardRef で囲み、外からrefを渡せるようにする。
  • inputタグのonChangeハンドラーもHiddenInputコンポーネントの外から渡せるようにする。

これで完成です。

豆知識

onChangeは連続で同じファイルを選択すると発火しない

二度同じファイルを選択したらonChangeは発火しないので注意してください。

状況としては、ファイルをstateで管理しているときに起こりえます。

  1. ファイルを選択、stateに保持。
  2. 先程のstateのファイルを削除
  3. もう一度同じファイルを選択 -> 何も起きない

試しに、以下で、ファイルを選択し、リセットボタン押して、もう一度同じファイルを選択してみてください。

以下のようにすると発火するようになります。

event.target.value = '';

Fileオブジェクトは列挙不可である

以下ができない

const a = JSON.stringify(file)
const b = {...file}

console.log(a)
console.log(b)

オブジェクトを拡張しながらコピーするやり方ができないので、何かしら対応方法を知っている方いましたら教えて下さい。

本当はこうやりたかった

const customFile = {...file, hoge: 'fuga'}

おまけ バックエンドへの保存方法

ファイルの保存は2通りの方法があります。

  1. バックエンドにファイルを送り、そこでいろいろ処理してストレージサービスに保存する
  2. フロントエンドから直接ストレージサービスに保存し、そのurlをバックエンドに送信する

個人的には方法2のほうをおすすめします。方法2はファイルの保存に関する処理をバックエンドに持たせる必要がないため、バックエンドのテストがしやすくなります。

ここらへんの方法に関しては以下の記事で説明しています。
https://zenn.dev/dove/articles/7321529d0fdbf1

Discussion

梅雨明け梅雨明け

こんにちは。参考になる記事ありがとうございます。

<button onClick={fileUpload}>ファイルアップロード</button>
<input
  hidden
  ref={inputRef}
  type="file"
  accept="image/*"
  onChange={onFileInputChange}
/>

の部分ですが、

<label htmlFor="file">ファイルアップロード</label>
<input
  id="file"
  hidden
  ref={inputRef}
  type="file"
  accept="image/*"
  onChange={onFileInputChange}
/>

のようにすると、inputタグのクリックイベント発火を簡単に記述できるのでもしよければ参考にしてみてください。