redux-form (2)  - Synchronous Validation Example

[redux-form (1) - Simple Form Example](https://qiita.com/sand/items/5ef1564546c130080398)
[redux-form (2) - Synchronous Validation Example](https://qiita.com/sand/items/7c24d811b8459839f767)
[redux-form (3) - Field-Level Validation Example](https://qiita.com/sand/items/0ce4078ba6fb443f0a54)
[redux-form (4) - Submit Validation Example](https://qiita.com/sand/items/3913526199b509770c6c)
[redux-form (5) - Initialize From State](https://qiita.com/sand/items/24bf124106761b2a8ace)
[redux-form (6) - ユーザ登録](https://qiita.com/sand/items/1ebf763ce98f71af41ec)

---

ReactでForm componentを作るときに、とても便利な**[redux-form](https://redux-form.com/8.2.2/)**の説明です。

**redux-form**の概説についてはまず以下の記事を参考にしてください。

[redux-form (1) - Simple Form Example](https://qiita.com/sand/items/5ef1564546c130080398)


# Synchronous Validation Example

[Synchronous Validation Example](https://redux-form.com/8.2.2/examples/syncvalidation/) - [Getting Started With redux-form](https://redux-form.com/8.2.2/docs/gettingstarted.md/)



**redux-form**を使うと、FormのValidationが簡単に実装できます。

今回はValidation function を使った、formの同期的なvalidationです。

validate関数はRedux state のvaluesを入力として、errorのobjectを返します。

同様にwarning function、以下の例ではwarn、を指定することもできます。


ソースを詳細に見ていくと、まず以下の例では、component propとして、renderField という関数が指定されてます。上の説明で言うと、2. stateless functionのパターンです。

またrenderFieldの引数(prop object)が、{ input,　label,　type,　meta: { touched, error, warning } }となっていることに注意してください。inputとmeta以外のlabelとtypeがcustom propsです。



```src/SyncValidationForm.js
import React from 'react'
import { Field, reduxForm } from 'redux-form'

// *** validation function
const validate = values => {
  const errors = {}
  if (!values.username) {
    errors.username = 'Required'
  } else if (values.username.length > 15) {
    errors.username = 'Must be 15 characters or less'
  }
  if (!values.email) {
    errors.email = 'Required'
  } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
    errors.email = 'Invalid email address'
  }
  if (!values.age) {
    errors.age = 'Required'
  } else if (isNaN(Number(values.age))) {
    errors.age = 'Must be a number'
  } else if (Number(values.age) < 18) {
    errors.age = 'Sorry, you must be at least 18 years old'
  }
  return errors
}

// *** warning function
const warn = values => {
  const warnings = {}
  if (values.age < 19) {
    warnings.age = 'Hmm, you seem a bit young...'
  }
  return warnings
}

// *** component prop : 2. A stateless function
const renderField = ({
  input,
  label,
  type,
  meta: { touched, error, warning }
}) => (
  <div>
    <label>{label}</label>
    <div>
      <input {...input} placeholder={label} type={type} />
      {touched &&
        ((error && <span>{error}</span>) ||
          (warning && <span>{warning}</span>))}
    </div>
  </div>
)


const SyncValidationForm = props => {
  const { handleSubmit, pristine, reset, submitting } = props
  return (
    <form onSubmit={handleSubmit}>
      <Field
        name="username"
        type="text"
        component={renderField}
        label="Username"
      />
      <Field name="email" type="email" component={renderField} label="Email" />
      <Field name="age" type="number" component={renderField} label="Age" />
      <div>
        <button type="submit" disabled={submitting}>
          Submit
        </button>
        <button type="button" disabled={pristine || submitting} onClick={reset}>
          Clear Values
        </button>
      </div>
    </form>
  )
}

export default reduxForm({
  form: 'syncValidation', // a unique identifier for this form
  validate, // *** validation function
  warn      // *** warning function
})(SyncValidationForm)
```


以下のindex.jsでは、オリジナルから不要と思われるものを削除し、最低限のものだけを残し見やすくしました。


```javascript:src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers } from 'redux'
import { reducer as reduxFormReducer } from 'redux-form'

const dest = document.getElementById('content')
const reducer = combineReducers({
  form: reduxFormReducer // mounted under "form"
})
const store = createStore(reducer)

const showResults = values =>
  new Promise(resolve => {
    setTimeout(() => {
      // simulate server latency
      window.alert(`You submitted:\n\n${JSON.stringify(values, null, 2)}`)
      resolve()
    }, 500)
  })

let render = () => {
  const SyncValidationForm = require('./SyncValidationForm').default
  ReactDOM.hydrate(
    <Provider store={store}>
      <h2>Form</h2>
      <SyncValidationForm onSubmit={showResults} />
    </Provider>,
    dest
  )
}

render()
```


# 実行画面

メールの入力エラーと、年齢が未入力で

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/7e3e93c2-904b-92e5-27e5-615977ac5542.png)


年齢制限もしっかりチェックできています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/7e3a7288-58c4-1a08-06fd-558342ec43d4.png)


全てのチェックがパスしてsubmitが成功しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/0207023b-3bbb-9b39-1167-83314e293061.png)


今回は以上です。