受け入れ可能なファイル形式を制限する
続いて、ファイル形式の制限に関する機能を解説します。
「画像ファイルだけをアップロードしたい」や「PDFファイルとEPUBだけを受け入れたい」といった要求仕様に応える機能は、冒頭で解説したaccept属性によって実現されています。ダイアログによるファイル選択であれば、「acceptで指定した形式のファイルしか選べない」という挙動になるので、受け入れないファイルについて考える必要はないのですが、ドラッグ&ドロップの場合は受け入れできないファイルがドロップされる可能性も考慮する必要があるので、いくつかのインターフェースが追加されています(リスト4)。
const {
getRootProps,
getInputProps,
// (5) 受け入れ済みのファイル一覧
acceptedFiles,
// (6) 受け入れできなかったファイル一覧
fileRejections,
} = useDropzone({
// (1) 受け入れ可能なファイル形式を指定する
accept: "image/*",
// (2) ドロップされたときのコールバック
onDrop: (acceptedFiles, fileRejections) => {},
// (3) 受け入れ可能なファイルがドロップされたときのコールバック
onDropAccepted: (acceptedFiles) => {},
// (4) 受け入れできないファイルがドロップされたときのコールバック
onDropRejected: (fileRejections) => {},
});
まずはinput要素と同様に、(1)でパラメータとしてacceptに受け入れ可能なファイル形式を指定します。application/jsonのようなMIME Type文字列でも構いませんし、.jpgのような拡張子でも構いません。また、カンマ区切りで複数のファイル形式を指定することもできます。詳しくはMDNをご覧ください。
受け入れ可能なファイル形式が指定された状態でドラッグ&ドロップを行うと、ファイルは 受け入れができたファイル一覧であるacceptedFilesと 受け入れできなかったファイル一覧であるfileRejectionsに仕分けされます。
両方のファイル一覧をコールバックで受け取れるのが、リスト3でも言及した(2)のonDropです。また、片方ずつ受け取りたい場合のコールバックとして、(3)のonDropAcceptedと(4)のonDropRejectedがあります。
フックの戻り値にも(5)のacceptedFilesや(6)のfileRejectionsがあるので、UIにファイル名を表示したい場合には、これらを扱うとよいでしょう。実際の使い方の例として、リスト3にファイル形式の制限を設定したのがリスト5です。
export function MyDropzoneBasicRejectable() {
const onDrop = useCallback((acceptedFiles, fileRejections) => {
console.log({ acceptedFiles, fileRejections }); // (2)
}, []);
const {
getRootProps,
getInputProps,
acceptedFiles, fileRejections, // (3)
} = useDropzone({
onDrop,
accept: "image/*" // (1)
});
return (
<div>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>
ファイルをここにドラッグアンドドロップするか、クリックしてファイルを選択してください
</p>
</div>
{acceptedFiles.length > 0 || fileRejections.length > 0 ? ( // (4)
<ul>
<li>受け入れられたファイル数: {acceptedFiles.length}</li>
<li>受け入れできなかったファイル数: {fileRejections.length}</li>
</ul>
) : null}
</div>
);
}
(1)で画像ファイルを受け入れられるように設定しました。(2)でonDropから受け取ったfileRejectionsをコンソールに出力しています。UI上でも結果が見やすいように、(3)でacceptedFilesとfileRejectionsを取り出し、(4)でそれぞれのファイル数を表示しています。実際に、PDFファイル、JPEGファイル、CSVファイルをドラッグ&ドロップしたのが図7です。
JPEGファイル1つが受け入れられ、PDFファイルとCSVファイルは受け入れられなかったことがわかります。この例ではテキストとして表示しましたが、受け入れられなかったファイルについて再選択を促すようなモーダルを表示したりするのもいいかもしれません。
受け入れ可能なファイル数を制限する
受け入れ可能なファイル形式の話題と似た話題として、受け入れ可能なファイル数も設定できます。一度にアップロードできるファイル数には限りがあるケースもあるので、こういった機能も重要ですね。フックのパラメータとしてmaxFilesを指定することで設定できます(リスト5)。
useDropzone({
// (1) 受け入れ可能なファイル数を指定する
maxFiles: 3,
});
(1)で最大3個として指定しているので、このファイル数以下であれば、すべてのファイルがacceptedFilesに入ります。一方で、このファイル数を超えると、すべてのファイルがfileRejectionsに入ります。ファイル数を制限したい場合に使うとよいでしょう。
スタイルを設定する
さて、最後にドラッグ&ドロップできる領域をもう少し格好よくする方法について解説しておきましょう(リスト6)。
// (1) 外枠の基本スタイル
const baseStyle = {
flex: 1,
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: "20px",
borderWidth: 2,
borderRadius: 2,
borderColor: "#eeeeee",
borderStyle: "dashed",
backgroundColor: "#fafafa",
color: "#bdbdbd",
outline: "none",
transition: "border .24s ease-in-out",
};
// (2) フォーカスが当たったときの枠の色
const focusedStyle = {
borderColor: "#2196f3",
};
// (3) 受け入れ可能なファイルをドラッグしたときの色
const acceptStyle = {
borderColor: "#00e676",
};
// (4) 受け入れできないファイルをドラッグしたときの色
const rejectStyle = {
borderColor: "#ff1744",
};
export function MyDropzoneStyled() {
const {
getRootProps,
getInputProps,
isFocused, // (5) フォーカス中にtrue
isDragAccept, // (6) 受け入れ可能なファイルをドラッグしたときにtrue
isDragReject, // (7) 受け入れできないファイルをドラッグしたときにtrue
} = useDropzone({ accept: "image/*" });
//(8)状態に応じたスタイルオブジェクトを生成する
const style = useMemo(
() => ({
...baseStyle,
...(isFocused ? focusedStyle : {}),
...(isDragAccept ? acceptStyle : {}),
...(isDragReject ? rejectStyle : {}),
}),
[isFocused, isDragAccept, isDragReject]
);
return (
<div>
<div {...getRootProps({ style })}>{/* (9)スタイルを適用する */}
<input {...getInputProps()} />
<p>
ファイルをここにドラッグアンドドロップするか、クリックしてファイルを選択してください
</p>
</div>
</div>
);
}
(1)では基本の状態を定義します。背景を少し灰色にして、内側には少しpaddingをつけました。また、外枠を破線にしつつ、角を少し丸めました。これだけで図8の見た目になります。
次に、状態に応じて外枠の色が変わるようにしてみましょう。クリックやタブキー入力などで、領域にフォーカスが当たった場合は(2)のスタイルで青くします。受け入れ可能なファイルがドラッグされた(マウスオーバーされた)場合には(3)のスタイルで緑にします。受け入れできないファイル形式がドラッグされた場合には(4)のスタイルで赤にします。
上記の状態に対応した戻り値がuseDropzoneには用意されています。フォーカス中には(5)のisFocusedがtrueになります(図9)。受け入れできるファイルがドラッグされた場合には(6)のisDragAcceptがtrueになり(図10)、受け入れできないファイルだった場合には(7)のisDragRejectがtrueになります(図11)。状態ごとに色が変わるように(8)でスタイルを切り替え、(9)でスタイルを当てています。
さほど複雑なコードを書かずに、こういったリッチなUIを実現できるのは、うれしいところですね。
まとめ
容易にドラッグ&ドロップ可能なファイル選択UIを提供できる、react-dropzoneについて解説しました。次回はステートマシンによる状態管理とビジュアライズが魅力的なXStateについて解説します。お楽しみに。
