はじめに
仕事で React のページ上においてフォームの値の変更を検知するコードを作成したのでその上で知ったことに関して備忘録代わりにこちらで記載しておきます。
その1:Formik 下での onChange 処理
まず1つ目は Formik 下での値の変更時の追加処理についてです。よくある Javascript であれば onChange で追加の処理を記述するだけで良いのですが、Formik の場合は少々状況が異なります。
まず前提知識として、Formik は React で良く使われるフォーム管理ツールであり、フォーム上のデータの管理やフォームに関する機能の提供を行っています。
Formik 提供の input となるコンポーネントでは onChange を気にせずに以下の様に利用しています。
<Formik
initialValues={{
email: 'test@example.jp'
}}
onSubmit={values => {
alert('submit!')
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<Field type="email" name="email"/>
<button type="submit">Submit</button>
</form>
)}
</Formik>
Field コンポーネントの実装上のプロパティでは onChange がありませんが、実際には Formik 上のデータに対する変更処理を行う関係で onChange は既に使用状態となっています。
そのため、Field コンポーネント上に用意した state 変数を変更する onChange を以下の様に実装すると、state 変数の変更だけが実行され、Formik 上で管理しているデータに値の変更が渡りません。
const [changed, setChanged] = useState(false)
// 中略
<Formik
initialValues={{
email: 'test@example.jp'
}}
onSubmit={values => {
alert('submit!')
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<Field type="email" name="email" onChange={() => {
if(!changed) setChanged(true)
}}/>
<button type="submit">Submit</button>
</form>
)}
</Formik>
そのため、Formik が本来想定している処理以外の state 変数の変更のような 追加処理を onChange で行う場合は、Formik への明示的なデータの受け渡しを以下の様に実装する必要があります。
const [changed, setChanged] = useState(false)
// 中略
<Formik
initialValues={{
email: 'test@example.jp'
}}
onSubmit={values => {
alert('submit!')
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<Field type="email" name="email" onChange={e => {
formik.setFieldValue("email", e.target.value) // <- 追加
if(!changed) setChanged(true)
}}/>
<button type="submit">Submit</button>
</form>
)}
</Formik>
その2:動的にフォームが増える場合の state 変数の対処
次に、ページ遷移時のレンダリング後に動的にフォームが増える場合の state 変数の対処について行った対応を記載します。
動的にフォームが増える例としては、リスト型のフォームなどが挙げられます。
具体的には、以下の様な要素を持つフォームが挙げられます。
・フォームの項目が後から動的に増やせる
・増やせる個数は一定ではない(DB側の制約による上限等はあり)
・増やした項目を更新前に消すことができる(必須ではない)
ページ遷移時のレンダリング後に動的に増えたフォームに合わせて state 変数を増やすような実装はエラーとなるため、まずお勧めできません。
そこで行うべき対策としては、配列の state 変数を用意するという方法です。
const [changedAry, setChangedAry] = useState([])
注意:TypeScript における単なる空配列の定義は never[] として解釈されてしまうので、以下の様に型の指定を付属した空配列の定義が必要です。
const [changedAry, setChangedAry] = useState([] as number[])
変数側としての定義は上述の通りで良く、各フォーム側で用意されているはずのユニークな name または id を配列に入れることで値を変更したかを設定できます。
更に、いずれかのフォームで変更があったかどうかについては
changedAry.length > 0
の条件で確認ができます。
また、指定の項目が変更されているかどうかは
changedAry.includes(xxxxx)
の条件で確認することができます。
終わりに
今回は React のページ上でフォームの値の変更を検知するに手段ついての備忘録を記載しました。
小手先の手法ではありますが、お役に立てればと思います。