React.jsのバリデーションライブラリ「Formik」がかなり人気を博しているようなので、早速勉強することにしました!
今回参考にさせて頂いた動画&記事はこちらです♫
Youtbe: Better React Forms with Formik
2020/02/26 追記
Formikの別記事を書きました。 → Formikを導入する
##何もライブラリを使用しなかったときのコード
シンプルですが記事タイトルと本文に入力文字数に関するバリデーションを設けたフォームを作成してみました。
▼ ソースコード
元になるソースコードも載せます。
あえて、かなり冗長的に作ってます。
import React, { Component } from 'react'
class Form extends Component {
constructor(props) {
super(props);
this.state = {
title: '',
errorTitle: '',
validTitle: true,
body: '',
errorBody: '',
validBody: true,
}
}
handleChangeTitle = e => {
const value = e.target.value
this.setState({
title: value,
errorTitle: ''
})
if (0 < value.length && value.length <= 100) {
this.setState({ validTitle: false })
} else {
this.setState({ validTitle: true })
}
}
handleBlurTitle = e => {
const value = e.target.value
if (value.length === 0) {
this.setState({ errorTitle: '記事タイトルを入力してください' })
} else if (value.length > 100) {
this.setState({ errorTitle: '記事タイトルは100文字以内で入力してください' })
} else {
this.setState({ errorTitle: '' })
}
}
handleChangeBody = e => {
const value = e.target.value
this.setState({
body: value,
errorBody: ''
})
if (0 < value.length && value.length <= 200) {
this.setState({ validBody: false })
} else {
this.setState({ validBody: true })
}
}
handleBlurBody = e => {
const value = e.target.value
if (value.length === 0) {
this.setState({ errorBody: '記事本文を入力してください' })
} else if (value.length > 200) {
this.setState({ errorBody: '記事本文は200文字以内で入力してください' })
} else {
this.setState({ errorBody: '' })
}
}
handleSubmit = e => {
e.preventDefault()
const { title, body, validTitle, validBody } = this.state
if (validTitle === false && validBody === false) {
this.props.addPost({ title, body })
this.setState({
title: '',
body: '',
validTitle: true,
validBody: true
})
} else {
if (title.length === 0) {
this.setState({ errorTitle: '記事タイトルを入力してください' })
} else if (title.length > 100) {
this.setState({ errorTitle: '記事タイトルは100文字以内で入力してください' })
} else {
this.setState({ errorTitle: '' })
}
if (body.length === 0) {
this.setState({ errorBody: '記事本文を入力してください' })
} else if (body.length > 200) {
this.setState({ errorBody: '記事本文は200文字以内で入力してください' })
} else {
this.setState({ errorBody: '' })
}
}
}
render() {
return (
<div>
<h5>記事を投稿</h5>
<form onSubmit={this.handleSubmit}>
<div>
<label htmlFor="title">記事タイトル</label>
<input
value={this.state.title}
type="text"
name="title"
onChange={this.handleChangeTitle}
onBlur={this.handleBlurTitle}
/>
<div>{this.state.errorTitle}</div>
</div>
<div>
<label htmlFor="body">本文</label>
<textarea
value={this.state.body}
name="body"
onChange={this.handleChangeBody}
onBlur={this.handleBlurBody}
rows="5"
/>
<div>{this.state.errorBody}</div>
</div>
<button type="submit">
投稿する
</button>
</form>
</div>
)
}
}
export default Form
………なげぇぇぇ〜〜〜〜っっ!!!😲😲😲
早速Formikを使っていきます!
##Formikを使う前準備
ライブラリのインストールからしていきます。formik公式が推奨しているバリデーションライブラリ、「yup」も一緒にインストールしてしまいます。
$ npm install formik yup --save
##Youtubeを見ながら実装
Youtube通りやったらかなり長い説明書になってしまいました😅
###⓪Formik用コンポーネントを作成&Form.jsを関数コンポーネントに書き換え
FormikForm.js
ファイルを新規作成して、Form.jsをラップするコンポーネントを作成しました。
import React from 'react'
import Form from './Form'
import * as yup from 'yup'
ここに書いていきます
export default FormikForm
Form.js
はclass構文で作っていましたが、今まで書いたstateやonChange、onBlurイベントを全部取っ払って関数コンポーネントにしました。
import React from 'react'
import { connect } from 'react-redux'
import { addPost } from '../actions'
const Form = () => {
return (
<div>
<h5>記事を投稿</h5>
<form>
<div>
<label htmlFor="title">記事タイトル</label>
<input
value={this.state.title}
type="text"
name="title"
/>
</div>
<div>
<label htmlFor="body">本文</label>
<textarea
value={this.state.body}
name="body"
rows="5"
/>
</div>
<button type="submit">投稿する</button>
</form>
</div>
)
}
export default connect(null, { addPost })(Form);
もちろん上ではthis.state.title
、this.state.body
使っちゃってますので、エラーが出るはずです。早速Formikタグを使っていきましょう!
###①withFormik
FormikForm.jsでFormikが提供しているwithFormik
を使っていきます。
まずは上で呼び出して、
import { withFormik } from 'formik'
const FormikForm = withFormik({
})(Form)
export default FormikForm
こんな感じ。波括弧内に、propsでtitle、bodyの初期値を設定するため、mapPropsToValues
を使っていきます。今回ダミーでtest1
とか入れてみました。
const FormikForm = withFormik({
mapPropsToValues() {
return {
title: 'test1',
body: 'test2'
}
}
})(Form)
###②Formik props: values
お次に先ほど指定した初期値がレンダーされるよう、Form.jsにvalues
を指定していきます。
const Form = ({
values
}) => {
return (
...
<input
value={values.title}
type="text"
name="title"
/>
<textarea
value={values.body}
name="body"
rows="5"
/>
...
)
}
保存したら初期値が表示されました!
###③初期値をpropsで渡す
初期値が空でなく、指定したデータを表示したい場合、<FormikForm title="デフォルトで表示した記事タイトル" body="デフォルトで表示したい記事本文" />
みたいに、propsで渡してあげることが出来るようです。
FormikForm.jsで直入力していた初期値を、変数名title
、body
をセットしましょう。
const FormikForm = withFormik({
mapPropsToValues({ title, body }) {
return {
title: title || '', // titleに値が入っていたらpropsで渡されたtitleを表示、または空表示
body: body || ''
}
}
})(Form)
表示が問題ないことが確認できます。
Form.jsにonChange
とonSubmit
イベントに関数をセットします。
const Form = ({
values,
handleChange,
handleSubmit
}) => {
return (
...
<form onSubmit={handleSubmit}>
<input
value={values.title}
type="text"
name="title"
onChange={handleChange}
/>
<textarea
value={values.body}
name="body"
rows="5"
onChange={handleChange}
/>
...
)
}
公式サイトでhandleSubmit
は下のように定義されています。
handleSubmit: (values: Values, formikBag: FormikBag) => void
FormikForm.jsにhandleSubmit
で引数のvalues
が取れるかどうか、console.logで確認することにします。
const FormikForm = withFormik({
mapPropsToValues({ title, body }){
...
},
handleSubmit(values){
console.log("values", values)
}
})(Form)
てきとうに値を入力して送信をクリックしたら、ちゃんとconsole.logでtitle
とbody
が出力してくれました!
ここまでで、バリデーションの無いフォームの挙動を実装できました。
###⑤<Form />
、<Field />
onSubmit
を実装しましたが、formタグをformikが提供している<Form />
に書き換えると省略することができます。
同様にinput
タグも<Field />
タグに書き換えることでonChange
を省くことが出来ます。
import { Form, } from 'formik'
…ここで予期しないことが起こりました。。
Form.jsでコンポーネントを作成してきましたが、FormikのForm
を使うとなると、コンポーネント名を変える必要があります。。ここまでForm.jsで進めてきましたが、コンポーネント名を変えます。
const PostForm = ({
values // ↓ handleSubmit、handleChangeを削除
}) => {
return (
...
<Form>
<Field
value={values.title}
name="title"
/>
<Field
value={values.body}
name="body"
rows="5"
component="textarea"
/>
</Form>
)
}
変わらずSubmitイベントが走ることを確認します。
###⑥バリデーションを作成
ここからバリデーションライブラリYup
を使っていくことになります。
最初にwithFormik
のvalidationSchema
をセットしていきます。
今回は「必須」と「最大文字数」を必須としたシンプルなアプリケーションなので、
required
max
を実装していきます。
const FormikForm = withFormik({
...
validationSchema: yup.object().shape({
title: yup.string().max(100, 'タイトルは100文字以内で入力してください').required('タイトルは必須項目です'),
body: yup.string().max(200, '本文は200文字以内で入力してください').required('本文は必須項目です')
}),
...
})(PostForm)
yup.object().shape({ ... })
のオブジェクト内にそれぞれのバリデーションの条件とエラーメッセージをセットで指定していくようです。
###⑦エラーメッセージを表示
PostForm.jsでerrors
をpropsで渡してエラーメッセージを表示するようにします。
エラーメッセージは{errors.インプットタグのnameの値}
です。
const PostForm = ({
values,
errors // 追加
}) => {
return (
...
<Field
value={values.title}
name="title"
/>
{errors.title && <div className="errorText">{errors.title}</div>}
<Field
value={values.body}
name="body"
rows="5"
component="textarea"
/>
{errors.body && <div className="errorText">{errors.body}</div>}
...
)
}
「ここまで実装したら大丈夫だろう」と思ったのですが、もう一踏ん張りのようです。
上記のコードだとこんな感じです。
なぜか入力していない本文のエラーメッセージまで表示されてしまいました。
###⑧touched
入力エリアにカーソルがクリックされたかどうかを、touched
がtrue/false
で判断してくれるのだそうな。
PostForm.jsにtouched
もpropsで渡していきます。
const PostForm = ({
values,
errors,
touched // 追加
}) => {
return (
...
{touched.title && errors.title && <div className="errorText">{errors.title}</div>}
...
{touched.body && errors.body && <div className="errorText">{errors.body}</div>}
...
)
}
「touched.title
、touched.body
がそれぞれtrueの時」をそれぞれ追加してあげることで、onBlur時のバリデーションチェックのような挙動になってくれます。
いい感じー!!🤗
###⑨handleSubmit
に実際の挙動を追加
すっかりすっかり忘れてましたが、handleSubmit
にReduxのActionで作成した「記事を追加する」関数を実行せねばならなかったのでした。。
console.log
としていたところを一回削除して、this.props.addPost
をForm側のコンポーネントに渡してあげたいと思います。
FormFormikコンポーネントの親コンポーネントにAction Creatorsを呼び出すconnectメソッドを実装し、
<FormikForm handleSubmit={props.addPost} />
のようにhandleSubmit
関数を定義しちゃいました。
▼ App.js
...
import { connect } from 'react-redux'
import { addPost } from '../actions'
const App = (props) => {
return (
<div>
<FormikForm handleSubmit={props.addPost} />
<Posts />
</div>
)
}
export default connect(null, { addPost })(App)
▼ FormFormik.js
const FormikForm = withFormik({
...
handleSubmit: (payload, { props, resetForm, setSubmitting }) => {
props.handleSubmit(payload) // 入力値の値がpayloadに入り、`props.handleSubmit`としてdispatchしています
setSubmitting(false) // これは何だろな?
resetForm() // 入力、送信後に入力エリアを空にする
}
})(PostForm)
export default FormikForm
##出来上がったソースコード
完成のgithub URLを記載します。
##【感想】
恐らくこのQiitaを読むよりYoutubeを観た方が理解が早いかも笑 Youtubeではチェックボックスやパスワードの場合も紹介していました。
正直公式ドキュメントは掻い摘んで(google翻訳しながら笑)見ただけなので、もっと実践寄りのFormik特集もやっていけたらと思っています。
Formikを突き詰めていきたいところですが…!
react-test-rendererについて理解を深めたいので、次回、テストについてQiitaの記事を書こうと思います。✍️(宣言しないと先延ばししそうなのであえて次記事宣言させて頂きました。😅)