<input type="file">はReactから直接Controlledにはできませんが、用途によってはそれに近い使い方ができます。
フォーム要素の役割
以前にも書きましたが、HTMLのフォーム要素にはいくつかの役割があります。
- ユーザーからの値の入力
-
設定した状態の保存(Controlledにする場合は無効化される) - 状態の表示
- フォーム送信時に値を伝える
このうち、「フォーム送信時に値を伝える」機能は、フォーム送信に頼らずAjaxを投げるような場面では不要となります1。
<input type="file">の場合
そして、ファイル入力の場合は「状態の表示」も不要となることが多いです。デフォルトの<input type="file">のデザインは、ボタンとファイル名だけのそっけないものでカスタマイズも効かず、「デフォルトの要素を完全に殺す or 透明化させるなどして、自前で描画する」ということもよく行われるような代物です。
そして、ふつうのtype="text"のような場合であれば、すでに入力された値から再編集させるためにも元の値の表示は必要ですが、type="file"では「すでにセットされたファイルから編集する」ような機能は通常存在せず、ファイルがあろうがなかろうが既存のファイルを選択してアップロードさせるだけですので、この意味合いでももとの値をセットさせる意味は薄いです。
ということで、<input type="file">の場合は「新規登録の機能さえあればそれでいい」という場面もじゅうぶん考えられます。
新規登録専用のものを作ってみる
新規登録以外の機能性が不要な場合、onChangeのタイミングでフォームをリセットすれば可能そうですが、1つ注意点があります。
function FileAppendInput({onChange, ...rest}) {
// リセット用のカウンター
const [counter, setCounter] = React.useState(0);
const handleChange = React.useCallback((e) => {
onChange(e);
// キーを変えて表示をリセット
setCounter(current => current + 1);
}, []);
return <input {...rest} type="file" key={counter} onChange={handleChange} />
}
バグか仕様かは不明なのですが、value = ''でリセットすると、Chromeで「onChangeのタイミングで回収したFileListの中身が書き換わる」という現象が発生してしまったので、それを避けるためにkeyの書き換えでフォームをリセットしています。
filesを書き戻したい場合
フォーム要素へのファイルの再セットが必要な用途の場合、useEffectでファイルの変化を監視して、変わったときにref経由でfilesへセットすれば問題なく実行できそうですが、ここにもまた問題点があります。filesへのセットに必要なFileListをどう用意するかです。
前の投稿にまとめましたが、FileからFileListを作成することが、iOSなど一部の環境では実現不可能なのです。このような環境で<input type="file">への書き戻しが必要な場合、最初にセットされたFileListそのものを引き回すより他に手段がなくなってしまいます。
-
ファイルについても、
FormDataにFileを追加して送信するという形で対応可能です。 ↩