概要
Reactで以下の内容を実装するサンプルです。ファイルは画像を前提にしています。
1.ファイルをドラッグするとファイルをプレビュー
2.登録ボタンを押すとstorageにアップロードし、最後にDB書き込み
インストール
ファイルをドラッグしてファイル情報を取得するコンポーネントは react-dropzone
を使います。
- react-dropzone
- https://react-dropzone.js.org/
firebaseもインストールします。
npm i -S react-dropzone
npm i -S firebase
オプショナルですがbootstrapを入れておきます
npm i -S bootstrap
ドラッグでファイル情報を取得する
まず、ファイルをドラッグしたらファイル情報を取得するところを実装します。
onDropはドラッグしたときに呼ばれる関数で、callbackを利用しています。
import React, { useState, useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import 'bootstrap/dist/css/bootstrap.min.css';
function App() {
const [uploadfile, setUploadfile] = useState();
const maxSize = 3 * 1024 * 1024;
//dropzone
const onDrop = useCallback((acceptedFiles) => {
if (acceptedFiles.length > 0) {
setUploadfile(acceptedFiles[0]);
}
}, []);
//initialize
const { acceptedFiles, getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({
onDrop,
accept: 'image/png, image/jpeg, image/gif, image/jpg',
minSize: 1,
maxSize,
});
return (
<div className="container m-5 text-center">
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>ファイルをドラッグするかクリックしてください。</p>
{uploadfile ? <p>選択されたファイル: {uploadfile.name}</p> : null}
</div>
</div>
);
}
export default App;
ファイルがドラッグされるとコールバックでonDropが呼ばれ、acceptedFielsのなかに、ファイル情報が入ってきます。
これをuploadfileというステートに入れてファイル情報を画面に表示しています。
実行すると、以下のように選択されたファイルが表示されます。

条件に一致しないファイルをエラーにする
initializeのところで、maxSize
とaccept
を指定しています。
maxSizeはドロップできる最大サイズ、acceptはファイルタイプを指定しています。
これを指定することで自動的にファイルをリジェクトしてくれます。
ドラッグすると、ファイルタイプが一致しない場合はisDragReject
がtrue
、ファイルサイズではねられるものはfileRejections
にエラー情報が入ってきます。
isDragReject
はドラッグ中のみ取得できます。
<div className="container m-5 text-center">
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>ファイルをドラッグするかクリックしてください。</p>
{uploadfile ? <p>選択されたファイル: {uploadfile.name}</p> : null}
{isDragReject ? <div className="alert alert-danger" role="alert">ファイルタイプが一致しません</div> : null}
{fileRejections.length > 0 ? <div className="alert alert-danger" role="alert">
{fileRejections[0].errors[0].message}
</div> : null}
</div>
</div>
fireRejections
に入っているerrors
がエラー情報です。codeとmessageが英語で入っているので、実際の実装時にはcodeをみてメッセージを表示すると良いと思います。

ドラッグした時にプレビュー表示する
次にプレビュー表示します。
画像を表示するためにファイル情報をURL形式にし、state(fileUrl)に設定します。
stateの設定とonDrop を次ように変更します。
const [fileUrl, setFileUrl] = useState();
//省略
const onDrop = useCallback((acceptedFiles) => {
if (acceptedFiles.length > 0) {
const src = URL.createObjectURL(acceptedFiles[0]);
setFileUrl(src);
setUploadFile(acceptedFiles[0]);
}
}, []);
取得した画像を表示する部分です
<div className="container m-5 text-center">
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>ファイルをドラッグするかクリックしてください。</p>
{uploadfile ? <p>選択されたファイル: {uploadfile.name}</p> : null}
{isDragReject ? <div className="alert alert-danger" role="alert">ファイルタイプが一致しません</div> : null}
{fileRejections.length > 0 ? <div className="alert alert-danger" role="alert">
{fileRejections[0].errors[0].message}
</div> : null}
+ <div className="card mt-2" style={{ margin: 'auto', width: '200px', height: '200px' }} >
+ <img src={fileUrl} width="100%" />
+ </div>
</div>
</div>
実行したところです。

storageにアップロードしてDBに書き込む
次にstroageにアップロードして、参照用のdownloadUrlを取得してDBに書きこみます。
firebase用の設定は割愛します。
作成したfirebaseの設定をimportで予めimportしておきます。
登録部分を実装します。
//省略
import firebase from './firebase'; //firebaseの設定
function App() {
//省略
const [message, setMessage] = useState();
//submit
const upload = async () => {
//fire upload
let url = "";
if (uploadfile.name) {
const storageref = firebase.storage().ref('sample/' + uploadfile.name);
const snapshot = await storageref.put(uploadfile);
url = await snapshot.ref.getDownloadURL();
}
//db updated
if (url) {
await firebase.firestore().collection('sample').doc().set({
filename: uploadfile.name,
fileUrl: url,
});
setMessage('登録しました');
}
}
先にstorageにアップし、アップした時にgetDownloadUrlで参照用のstorageのURLを取得します。
それをfirestoreに登録しています。
登録ボタンと登録後のメッセージ表示の部分です。
<div className="container m-5 text-center">
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>ファイルをドラッグするかクリックしてください。</p>
{uploadfile ? <p>選択されたファイル: {uploadfile.name}</p> : null}
{isDragReject ? <div className="alert alert-danger" role="alert">ファイルタイプが一致しません</div> : null}
{fileRejections.length > 0 ? <div className="alert alert-danger" role="alert">
{fileRejections[0].errors[0].message}
</div> : null}
<div className="card mt-2" style={{ margin: 'auto', width: '200px', height: '200px' }} >
<img src={fileUrl} width="100%" />
</div>
</div>
+ <button type="button" className="btn btn-primary mt-2" onClick={upload}>登録</button>
+ {message ? <div className="alert alert-success mt-2" role="alert">{message}</div> : null}
</div>
実行してみた画面です。

storageにもfirestoreにも書き込まれています。
以上です。