React-Hooks-Formを使って、hiddenにしたinputからファイル(画像)をアップロードしたい
タイトルの通り、RHFを使ってhiddenしたinput要素からfileをアップロードできるようにしたい。
前提
- 上記の技術スタック
- useRefを使ってhiddenになったinput要素を紐付けた要素からクリックできる
hiddenFileInput
をrefとして扱っている - RHFを使うためにrefでregisterしている
- button要素のtype属性をsubmit、もしくは指定なし(記述なし)にするとform要素のonSubmitイベントが発火されて想定と違う挙動が起こるので type='button'にしてる。
エラー/現象
useRefで紐付けたinputをbuttonからクリックしてもファイル選択画面が開かない
うまくいっていること/わかったこと
- console.log('Clicked!')でhandleFileClickクリックイベント自体は発火できていることを確認
- console.log(hiddenFileInput.current?);の出力がnullなのでhiddenFileInputが参照できていない
- おそらく ref={{ register } && { hiddenFileInput }} の指定方法が怪しい
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
mode: 'onChange',
criteriaMode: 'all',
shouldFocusError: false,
});
const hiddenFileInput = useRef<HTMLInputElement>(null);
const handleFileClick = () => {
hiddenFileInput.current?.click();
console.log(hiddenFileInput.current);
console.log('Clicked!');
};
return(
<form onSubmit={handleSubmit(handleUpdateComplete)}>
<input className='hidden' type='file' name='files' ref={{ register } && { hiddenFileInput }} accept='image/*' />
<button onClick={handleFileClick} type='button'>
変更する
</button>
</form>
)
変更
<input className='hidden' type='file' name='files' ref={hiddenFileInput} {...register} accept='image/*' />
Similar case
わかったこと
- registerにはrefもonChangeも含まれている(nameも)
- 別で指定すると上書きされる
- formのonSubmitから呼び出す関数を発火できない(input要素がregisterに登録されていないから)
対策 / nextアクション
このトレードオフ的なジレンマを乗り越えるために・・・
- hiddenされたinputの操作はuseRefを使わずにlabelの
htmlfor
とid
の関係性で操作するよう実装 - onChangeを使わずにuseFormから即座にstate更新を反映するやり方を調べる
1. hiddenされたinputの操作はuseRefを使わずにlabelのhtmlforとidの関係性で操作するよう実装
<form onSubmit={handleSubmit(handleUpdateComplete)}>
<input
className='hidden'
type='file'
id='avatar-image' //追加
// name='photoURL' //削除
{...register('photoURL')}
// onChange={handleFileChange} //削除
// ref={hiddenFileInput} //削除
accept='image/*'
/>
<input type='submit' className='hidden' ref={hiddenTextInput} />
<label
className='absolute top-[140px] left-[390px] text-sm mb-4 text-gray-400 hover:text-gray-500 rounded cursor-pointer focus:outline-none focus:ring-penn-green focus:ring-2 focus:ring-opacity-50'
// onClick={handleFileClick} //削除
htmlFor='avatar-image' //追加
>
変更する
</label>
</form>
ちなみにref={register}はRHFのv7で上記のようにスプレッド使うようになった
例えば以下のようにonChangeを...registerの先に持ってくるとエラーになる
<input
・・
onChange={handleFileChange}
{...register('photoURL')}
・・
/>
エラー
'onChange' が複数回指定されているため、ここでの使用は上書きされます。ts(2783)
UserProfile.tsx(96, 19): このスプレッドは、常にこのプロパティを上書きします
2. onChangeを使わずにuseFormから即座にstate更新を反映するやり方を調べる
→自分の作ったonChangeをRHFの書式に合わせて使う
検索「react hook form onchange not working」
> How to can I use onChange on React Hook Form Version 7.0
In register documentation https://react-hook-form.com/api/useform/register, sample exists on Custom onChange, onBlur section :
サンプル
// onChange got overwrite by register method
<input onChange={handleChange} {...register('test')} />
// register's onChange got overwrite by register method
<input {...register('test')} onChange={handleChange}/>
const firstName = register('firstName', { required: true })
<input
onChange={(e) => {
firstName.onChange(e); // method from hook form register
handleChange(e); // your method
}}
onBlur={firstName.onBlur}
ref={firstName.ref}
/>
実装してみる
<input
className='hidden'
type='file'
id='avatar-image'
// name='photoURL'
// ref={register}
{...register('photoURL')}
onChange={(e) => {
register('photoURL').onChange(e)
handleFileChange(e);
}}
// ref={hiddenFileInput}
accept='image/*'
/>
できた!
ちゃんとドキュメント読んでおこう。。。。。
これでrefも色々カスタマイズできるのかな
// not working, because ref is not assigned
<TextInput {...register('test')} />
const firstName = register('firstName', { required: true })
<TextInput
onChange={firstName.onChange}
onBlur={firstName.onBlur}
inputRef={firstName.ref} // you can achieve the same for different ref name such as innerRef
/>
// correct way to forward input's ref
const Select = React.forwardRef(({ onChange, onBlur, name, label }, ref) => (
<select name={name} ref={ref} onChange={onChange} onBlur={onBlur}>
<option value="20">20</option>
<option value="30">30</option>
</select>
));