React Hook FormのControllerを使用した際の<input type='file' />の問題と対処法
React Hook Form の Controller コンポーネントを使用すると、カスタムフォームコンポーネントを簡単に統合できるため便利ですが、input type="file"との組み合わせで問題が発生する場合があります。
問題点
Controller を使用して input type="file"をラップした際に、フォームの送信時に返されるデータがファイルの実際のデータではなく、ファイルパスの文字列として返ってくる場合がある。
さらに、setValue でファイルの値を設定しようとすると、以下のようなエラーが発生する。
InvalidStateError: Failed to set the 'value' property on
'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string.
原因
input type="file"の value 属性はセキュリティ上の理由から、JavaScript から変更することが許可されてないので、React Hook Form の Controller が内部でファイル入力の値を制御しようとすると、上記のエラーが発生します。
解決策
ファイルのアップロードを適切に扱うには、以下の手順で可能になります。
"use client";
import { Controller, useForm } from "react-hook-form";
export default function Home() {
const {
handleSubmit,
formState: { errors },
control,
} = useForm();
const onSubmit = (data: any) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="file"
defaultValue=""
render={({ field: { onChange } }) => (
<>
<label htmlFor={`checkbox-file`}>file</label>
<input
id={`checkbox-file`}
type="file"
onChange={(e) => onChange(e.target.files)}
/>
</>
)}
/>
{errors.exampleRequired && <span>This field is required</span>}
<input type="submit" />
</form>
);
}
Controller の render プロップを使用してカスタムレンダリングを行い、input type="file"の onChange イベントでファイルデータを取得します。
ファイルデータを state などに一時的に保存します。
必要に応じて、setValue を使用してファイルデータをフォームの状態にセットします。
ただし、input type="file"の value には直接セットしないよう注意してください。
この方法を使用することで、input type="file"の扱いで生じる問題を回避しながら、ファイルのアップロードを適切に処理することができます。
まとめ
React Hook Form の Controller を使用する際に input type="file"の問題に直面した場合、上記の方法で問題を回避できます。簡単な対処法を知っておくだけで、フォームの開発がよりスムーズに進められるでしょう。
Discussion
inputのtypeがfileの時にbuttonで発火するようにしてみました。
また、
useFieldArray
を使って複数のファイルをハンドリングするデモもざっくり作ってみました。