はじめに
この記事はPOL AdventCalenderの8日目の記事です。
こんにちは!株式会社POLでフロントエンドエンジニアをしております、橋本ことブリボンと申します!
弊社では、フロントエンドはReactを用いて開発をしております。
みなさんは、Reactでformを作る際、どのように実装しますか?
ライブラリーを使わずに実装する、Formikを使う、redux-formを使うなどなど……
弊社では、Formikを使ってformの実装しております。
また、formのバリデーションを実装するために、 Yup というライブラリーを使っているのですが、Formikの説明だけに注力したいため、詳細な説明は致しません。
別の記事で、Yupを使ったformのバリデーションの解説をしようと思っております。
Formikとは何か?
Formikとは、 Reactでformをより簡単で簡潔に実装できるようにしてくれるライブラリーです。
公式ドキュメント
Formikの特徴として、
- formのvalueを管理してくれる
- エラーのハンドリングと管理もできる
- バリデーションを簡単に設定できる
- onChangeやonSubmitなどのイベントハンドラを提供してくれる
- idもしくは、name属性を与えることで、どの要素を変更したのかの検知ができる
主に、上記5つの特徴を備えています。
早速、実装の解説に移りましょう。
わかりやすくするために基本編では、ログインフォームのフロントだけの実装を例にして解説していきます。
※reduxは一切出しません。
必要な要素は、メールアドレスとパスワードのinput要素だけです。
Formik[基本編]
Formikとyupをプロジェクトへインストール
yarn add formik yup
これだけですね!
Formikの実装
以下のコードはサンプルコードです。
メールアドレスのname属性はemail、
パスワードのname属性はpasswordと設定しています。
Formikでは、 Formikと呼ばれるコンポーネントにいくつかpropsを渡すことで、formの実装をします。
import React from "react";
import { Formik } from "formik";
const Login = () => {
return (
<Formik
initialValues={{ email: "", password: "" }}
onSubmit={values => console.log(values)}
render={(props) => (
<form onSubmit={props.handleSubmit}>
<div>
<label>email</label>
<input
name="email"
value={props.values.email}
onChange={props.handleChange}
/>
</div>
<div>
<label>password</label>
<input
type="password"
name="password"
value={props.values.password}
onChange={props.handleChange}
/>
</div>
<button type="submit">送信</button>
</form>
)}
/>
);
};
export default Login;
initialValues
読んで字の如く、初期値です。
デフォルトでは何も入らないことを想定していますので、空文字列を入れています。
※書きながら思いついたのですが、初期値が必要ない場合、initialValuesを定義する必要はないかもしれません。
仮に、以下のように初期値を渡すと、下の画像の通りになります。
initialValues={{ email: "test@example.com", password: "" }}
onSubmit
こちらもinitialValuesと同じく、名前から役割がわかるのではないでしょうか。
onSumbitイベントが発火した時に、どういう処理を行うか記述する箇所です。
**{}**の中は関数で定義し、引数にはformのvaluesを渡します。
とりあえず、ここではAPIを叩くということもしないので、consoleにデバッグしています。
formにメールアドレスとパスワードを入力し、「送信」ボタンを押すと、入力したものがデバッグされているのが確認できます。
render
Formikの肝であり中身であるrender。
RenderPropsを使っていることもあり、少しわかりづらい実装となっております。
まず、renderはpropsを持ちます。
中身は、values, errors, handleChange, handleSubmit, handleBlur……などなど。
ここで必要なのは、valuesとerrorsとhandleChangeとhandleSubmitです。
valuesには、formに入力したvalueが入り、handleChangeとhandleSubmitはそれぞれ、formのvalueの変更と送信のハンドラーです。
formタグのonSubmitイベントにhandleSubmitを、formの要素のonChangeイベントにhandleChangeを渡すことで、それぞれのハンドラーを使用することができます。
valuesはformの要素に渡さなくても機能します。
初期値を何かしら渡す必要がある時にだけ、渡せば良いです。
※知識不足で申し訳ないですが、idかnameを指定するだけで初期値が入力されるかも。曖昧ですみません(><)
最初の方でも述べましたが、formの要素にはidかname属性、どちらかを指定してください。
私は、name属性を指定する方を好みます。
name属性を指定することで、Formikがどの要素を変更したかを検知できます。
バリデーションの実装
サンプルコードのみを提示します。
画像のようにエラーメッセージが表示されます。
import React from "react";
import styled from "styled-components";
import { Formik } from "formik";
import * as Yup from "yup";
const Error = styled.span`
color: red;
`;
const validation = () =>
Yup.object().shape({
email: Yup.string()
.email("メールアドレスの形式で入力してください")
.required("必須項目です"),
password: Yup.string().required("必須項目です")
});
const Login = () => {
return (
<Formik
initialValues={{ email: "", password: "" }}
validationSchema={validation()}
onSubmit={values => console.log(values)}
render={(props) => (
<form onSubmit={props.handleSubmit}>
<div>
<label>email</label>
<input
name="email"
value={props.values.email}
onChange={props.handleChange}
/>
<Error>{props.errors.email}</Error>
</div>
<div>
<label>password</label>
<input
type="password"
name="password"
value={props.values.password}
onChange={props.handleChange}
/>
<Error>{props.errors.password}</Error>
</div>
<button type="submit">送信</button>
</form>
)}
/>
);
};
export default Login;
駆け足ではありますが、以上がFormikの基本的な使い方です。
以降ではもう一歩踏み込んで、再利用可能性を高めるためのコンポーネント分割を行っていきます。
そのために、必要なメソッドがあるので順次解説していきます。
Formik[ちょっと応用編]
Formikの悩み
ログインフォームでは、基本的にメールアドレスとパスワードのみの実装で済むので、比較的コードがぐちゃぐちゃになりません。
それでも、上記の例のような書き方をすると、Container componentとPresentational componentが混在しており、やや見辛いコードとなっております。
また、検索フォームや新規登録フォームではどうでしょう?
たくさんのformの要素を使いますよね。
私自身、Formikで可読性や再利用生の高いコードをどう書こうかかなり悩みました。
特に、コンポーネント分割をする方法です。
分割したformのコンポーネントに、handleChangeやvaluesやnameをpropsとして渡せばうまく実装できそうな気がします。
ただ、全コンポーネントでhandleChangeやvaluesを渡すことに、まどろっこしさと抵抗を感じます。
Formikでこれらを簡潔に実装するためにはどうすれば良いでしょう。
検索フォームや新規登録フォームの実装は複雑になりがちなので、これ以降はお問い合わせフォームでの実装を考えます。
なお、以降のお問い合わせフォームは、私がFormikを解説するためだけの目的で作ったものです。
現実には存在しませんので、ご了承を。
お問い合わせフォームの形式
以下のスクショのフォームを作成します。
実装はこんな感じですね。
import React from "react";
import styled from "styled-components";
import { Formik } from "formik";
import * as Yup from "yup";
const Error = styled.span`
color: red;
`;
const validation = () =>
Yup.object().shape({
trigger: Yup.string().required("必須項目です"),
bothering: Yup.string().required("必須項目です"),
wishing: Yup.string().required("必須項目です"),
others: Yup.string().required("必須項目です")
});
const Contact = () => {
return (
<Formik
initialValues={{ trigger: "", bothering: "", wishing: "", others: "" }}
validationSchema={validation()}
onSubmit={values => console.log(values)}
render={({ errors, handleChange, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label>サービスを知ったきっかけは何か</label>
<input name="trigger" onChange={handleChange} />
<Error>{errors.bothering}</Error>
</div>
<div>
<label>困っていること</label>
<input name="bothering" onChange={handleChange} />
<Error>{errors.trigger}</Error>
</div>
<div>
<label>お願いしたいこと</label>
<input name="wishing" onChange={handleChange} />
<Error>{errors.wishing}</Error>
</div>
<div>
<label>その他</label>
<input name="others" onChange={handleChange} />
<Error>{errors.others}</Error>
</div>
<button type="submit">送信</button>
</form>
)}
/>
);
};
export default Contact;
上のコードを見て何か気づきますか?
name属性だけが違うinputタグが4つもあります。
早速、これらをDRYに書いていきましょう!
コンポーネント分割
name属性とhandleChangeを受け取るInput.jsを作りました。
再利用生を考え、Input.jsはerrorも受け取ってエラーメッセージを返すコンポーネントとしました。
import React from "react";
import styled from "styled-components";
const Error = styled.span`
color: red;
`;
const Input = ({ name, error, handleChange }) => (
<div>
<input name={name} onChange={handleChange} />
<Error>{error}</Error>
</div>
);
export default Input;
このコンポーネントをimportして使ってみましょう!
import React from "react";
import { Formik } from "formik";
import * as Yup from "yup";
import Input from "./Input";
const validation = () =>
Yup.object().shape({
trigger: Yup.string().required("必須項目です"),
bothering: Yup.string().required("必須項目です"),
wishing: Yup.string().required("必須項目です"),
others: Yup.string().required("必須項目です")
});
const Contact = () => {
return (
<Formik
initialValues={{ trigger: "", bothering: "", wishing: "", others: "" }}
validationSchema={validation()}
onSubmit={values => console.log(values)}
render={({ handleChange, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label>サービスを知ったきっかけは何か</label>
<Input
name="trigger"
error={errors.trigger}
handleChange={handleChange}
/>
</div>
<div>
<label>困っていること</label>
<Input
name="bothering"
error={errors.bothering}
handleChange={handleChange}
/>
</div>
<div>
<label>お願いしたいこと</label>
<Input
name="wishing"
error={errors.wishing}
handleChange={handleChange}
/>
</div>
<div>
<label>その他</label>
<Input
name="others"
error={errors.others}
handleChange={handleChange}
/>
</div>
<button type="submit">送信</button>
</form>
)}
/>
);
};
export default Contact;
これだけでも、だいぶ変わりましたね!
4つのformの要素が同じようなデザインとなる場合、Input.jsを変更するだけでよくなりました!
しかし、propsでname, error, handleChangeの3つを送ることは、まだまだ冗長な記述になっているし、Formikからしたら無駄なことをしています。
もっと便利な方法を模索しましょう。
Fieldコンポーネントを使う
どの要素にも必ず必要なhandleChangeをすべての要素でpropsとして渡しているのは、無駄なことだと思いませんか?
また、共通コンポーネントで各々のコンポーネントのerrorを出力する方法があったとしたら……?
FormikのFieldコンポーネントを使うと、handleChangeやerrorをpropsとして送る必要がなくなります。
import React from "react";
import styled from "styled-components";
import { Field } from "formik";
const Error = styled.span`
color: red;
`;
const Input = ({ name }) => (
<Field name={name}>
{({ field, form: { errors } }) => (
<div>
<input {...field} />
<Error>{errors[name]}</Error>
</div>
)}
</Field>
);
export default Input;
import React from "react";
import { Formik } from "formik";
import * as Yup from "yup";
import Input from "./Input";
const validation = () =>
Yup.object().shape({
trigger: Yup.string().required("必須項目です"),
bothering: Yup.string().required("必須項目です"),
wishing: Yup.string().required("必須項目です"),
others: Yup.string().required("必須項目です")
});
const Contact = () => {
return (
<Formik
initialValues={{ trigger: "", bothering: "", wishing: "", others: "" }}
validationSchema={validation()}
onSubmit={values => console.log(values)}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label>サービスを知ったきっかけは何か</label>
<Input name="trigger" />
</div>
<div>
<label>困っていること</label>
<Input name="bothering" />
</div>
<div>
<label>お願いしたいこと</label>
<Input name="wishing" />
</div>
<div>
<label>その他</label>
<Input name="others" />
</div>
<button type="submit">送信</button>
</form>
)}
/>
);
};
export default Contact;
必要なpropsは、nameだけです。
nameをInput.jsに渡せば、どの要素を変更するか、どの要素でエラーが発生しているかが、Formik側で検知してくるれます。
少しだけ解説をすると、field propsにはname, value, onChangeが、formにはvalues, errorsなどの値が入っています。
※デバッグしてみると面白いです。
connectを使う
Fieldコンポーネントと同じことをconnectメソッドで行うことができます。
むしろ、こちらの方が直感的で簡潔な表現ができるのではないでしょうか。
import React from "react";
import styled from "styled-components";
import { connect } from "formik";
const Error = styled.span`
color: red;
`;
const Input = ({ name, formik: { handleChange, errors } }) => {
return (
<div>
<input name={name} onChange={handleChange} />
<Error>{errors[name]}</Error>
</div>
);
};
export default connect(Input);
connectを使うことで、該当のコンポーネントにFormikで使用する変数やメソッドを、propsとして渡すことができます。
終わりに
このように、Formikには様々なことができるようにpropsやメソッド、コンポーネントが用意されています。
ここで紹介したものは、その一部でしかありません。
例えば、setFieldValueというメソッドを使うことで、条件に応じてvalueを変更することができ、より柔軟なformの実装をすることが可能となります。
同じようにsetFieldErrorでは、柔軟にエラーハンドリングを行うことができます。
私自身、まだまだFormikのドキュメントを読み込んでいないので、Formikの真価を発揮できているとは言い難いと思ってます。
今回解説した内容で、疑問に思う方、こういうやり方あるよーというのを知っている方、いやいやここ間違っとるやんと思う方、どしどしコメントお待ちしております!
また、POLでは私たちと一緒にサービスを作っていく仲間を募集しております。
弊社のフロントエンドは特に、まだまだ環境が整いきってません。
ぜひ、お力を貸して欲しいと思っております。
この記事を見てPOLに興味を持った方、アドバイスを持っている方、ぜひぜひお気軽にお声がけください!