はじめに
Reactでフォームを扱う時どのように扱っていますか?ReactのAPIを用いたり、recoilなどの状態管理ライブラリを用いるなど様々な方法があると思います。その中でも私はReact Hook Formを用いて管理することが多いです。
この記事ではそんなReact Hook Formのバージョン7.43.0で追加されたグローバルエラーについて解説していきます。バージョン7.43.0ではある条件で型に関するバグがあるのでバージョン7.43.1以降を利用するようにしましょう(後述)。
この記事はこちらのissueによって提案された機能の解説となっています。
グローバルエラー
グローバルエラーは特定のフォームフィールドに紐づいていないエラーを指します。サーバーエラーやカスタムエラーなどに該当します。
これまで
グローバルエラーは今回正式にサポートされましたが、これまでも扱うことが出来ました。
export const SampleForm = (): JSX.Element => {
const { handleSubmit, clearErrors, setError } = useForm();
const onSubmit = () => {
clearErrors();
try {
fetch(...);
} catch(e: FetchError) {
setError('server', { type: 'serverError', message: e.message });
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" name="hello" />
<button>送信</button>
</form>
);
};
このコードは問題なく動作しますが一点DX上のデメリットがあります。
通常のエラーは再送信する時に自動的に一度エラーをリセットしますが、グローバルエラーはフォームフィールドに紐づいていないので再送信前にclearErrors
でエラーを手動で削除する必要があると言う点です。このような実装はライブラリの仕様を知らないとバグに繋がるなど保守の面でも不利な性能を持つので積極的に使いたい手法ではないと考えています。
さらに、以下のようにuseForm
により強い型制約を渡した場合にグローバルエラーをセットすると型エラーが出てしまう問題があります。
export const SampleForm = (): JSX.Element => {
const {
handleSubmit,
clearErrors,
setError
} = useForm<{ hello: string }>();
const onSubmit = () => {
clearErrors();
try {
fetch(...);
} catch(e: FetchError) {
// ここでエラーが出ます(型 '"server"' の引数を型 '"hello"' のパラメーターに割り当てることはできません。)
setError('server', { type: 'serverError', message: e.message });
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" name="hello" />
<button>送信</button>
</form>
);
};
useForm
にフォームフィールドの型情報{ hello: string; }
を渡しているので、setError
に始まりあらゆる関数のname
を要求する引数は'hello'
だけになります。つまりグローバルエラーを扱いたいときはダミーの型情報も合わせて渡すか、緩い条件で扱うことが求められます。
7.43以降
これを解決したのが今回追加された機能です。この機能はReact Hook Formで扱えるエラーのname
にフォームフィールドの他に'root'
や`root.${string}`
を渡すことを可能にするというものです。これによって先ほどのコードは以下のように書けるようになります。
export const SampleForm = (): JSX.Element => {
const { handleSubmit, setError } = useForm<{ hello: string }>();
const onSubmit = () => {
try {
fetch(...);
} catch(e: FetchError) {
setError('root.server', { type: 'serverError', message: e.message });
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input type="text" name="hello" />
<button>送信</button>
</form>
);
};
型の制約によるエラーやセットしたエラーのリセットもする必要がないので、他のフォームフィールドと同じように扱うことができるようになりました。
バージョン7.43.0ではuseForm
に型を渡した時は型エラーで設定できないことに注意してください(7.43.1で修正されました)。
おわりに
これまで私はグローバルなエラーを扱うときはこの記事で紹介したデメリットと天秤にかけてReact Hook Formとは別で用意した状態で管理していたので、この機能でフォームに関するエラーを一つの源からで扱えるようになって綺麗に書くことができるようになりとても嬉しいです。