redux-form (6) - ユーザ登録

[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)


今回は少し実用を意識して、ユーザ登録を実装します。UIコンポネントを作成するために**antd**を使います。
[React UI library の antd について (1) - Button](https://qiita.com/sand/items/35422a669e6021cccb4f)




# ユーザ登録

## 画面イメージ

画面イメージです。**antd**を使っているので、簡単にアイコンで装飾できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/265cb0bc-b0f0-1de1-1029-136f97f7c162.png)


## 環境設定

まずは開発環境を構築します。

```
yarn create react-app antd-demo
cd antd-demo
yarn add redux react-redux redux-form redux-logger antd
```

念のため、package.jsonを掲載しておきます。

```json:package.json
{
  "name": "antd-demo",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd": "^3.20.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-redux": "^7.1.0",
    "react-scripts": "3.0.1",
    "redux": "^4.0.1",
    "redux-form": "^8.2.4",
    "redux-logger": "^3.0.6"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}
```


## ソースコード

ユーザの配列を保持する Redux state を定義します。ユーザを追加するだけの簡単なものにします。

```JSX:src/reducers
const users = (state = [], action) => {
  switch (action.type) {
    case 'ADD_USER': // *** userを追加
      return [
        ...state,    // *** 分割代入、stateに追加
        {
          email: action.user.email,
          name: action.user.name,
          password: action.user.password
        }
      ]
    default:
      return state
  }
}

export default users
```

これもユーザを追加するactionだけを定義します。

```JSX:src/actions.js
export const addUser = user => ({
  type: 'ADD_USER',
  user: user
})
```

index.jsです。ユーザ登録のみの簡単な機能しかありませんが、**redux-logger** と **redux-form**を組み合わせているので、少し複雑かもしれません。

```JSX:src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { reducer as reduxFormReducer } from 'redux-form'
import logger from 'redux-logger'
import Register from './Register'
import users from './reducers'
import { addUser } from './actions'

const dest = document.getElementById('root')
const reducer = combineReducers({
  users,
  form: reduxFormReducer
})
const store = createStore(
    reducer,
    applyMiddleware(logger)
)

const showResults = values =>
  new Promise(resolve => {
    setTimeout(() => {
      // simulate server latency
      console.log(`submitted:${JSON.stringify(values, null, 2)}`)
      store.dispatch(addUser(values));
      resolve()
    }, 500)
  })

// redux-formを使っているのでProviderは必要
let render = () => {
  ReactDOM.hydrate(
    <Provider store={store}>
      <Register onSubmit={showResults} />
    </Provider>,
    dest
  )
}

render()
```

ユーザ登録画面の構築には、antdとredux-formを組み合わせて作っているので、少し複雑です。

* Register componentの大枠は**antd**の**Form**を利用しています。
* Formの子要素には**redux-form**の**Field** を利用しています。
* 更にFieldのcomponent属性に**antd**の**Input**（で作られたcomponent）を利用しています。

antdとredux-formを組み合わせについては、以下の記事も参照ください。
[React UI library の antd について (3) - redux-form](https://qiita.com/sand/items/a0cf3fa3a1a17741c06b)

redux-formによるvalidateはシンプルなのでいいですね。


```JSX:src/Register.js
import React from 'react';
import PropTypes from 'prop-types';
import { Form, Icon, Button } from 'antd';
import { Field, reduxForm } from 'redux-form';
import renderInput from './input';
import 'antd/dist/antd.css';

import './style.css';

const RegisterForm = props => {
  const { handleSubmit, pristine, submitting, message } = props;

  return (
    <Form onSubmit={handleSubmit} className="form-register-containers">
        <h1 className="center">
          ユーザ登録
        </h1>
        <Field
          name="email"
          hasFeedback
          component={renderInput}
          disabled={submitting}
          label="メールアドレス"
          placeholder="メールアドレス"
          prefix={<Icon type="mail" style={{ color: 'rgba(0,0,0,.25)' }} />}
        />
        <Field
          hasFeedback
          type="password"
          name="password"
          component={renderInput}
          disabled={submitting}
          label="パスワード"
          placeholder="パスワード"
          prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
        />
        <Field
          hasFeedback
          type="password"
          name="confirmPassword"
          component={renderInput}
          disabled={submitting}
          label="確認用パスワード"
          placeholder="確認用パスワード"
          prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
        />
        <Field
          hasFeedback
          name="name"
          component={renderInput}
          disabled={submitting}
          label="お名前"
          placeholder="お名前"
          prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
        />
        <Form.Item className="center">
          <Button
            type="primary"
            htmlType="submit"
            className="btn-submit"
            disabled={pristine || submitting}
          >
            ユーザ登録
          </Button>
        </Form.Item>
        {!!message && <p className="caption-invalid">{message}</p>}
    </Form>
  );
};

// redux-formのvalidate
const validate = values => {
  const errors = {};
  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.password) {
    errors.password = "Password can't be blank";
  }

  if (!values.confirmPassword) {
    errors.confirmPassword = "Confirm password can't be blank";
  }

  if (
    values.password &&
    values.confirmPassword &&
    values.password !== values.confirmPassword
  ) {
    errors.confirmPassword = "Confirm password didn't match";
  }

  if (!values.name) {
    errors.name = "Name can't be blank";
  }

  return errors;
};

RegisterForm.propTypes = {
  pristine: PropTypes.bool,
  message: PropTypes.string,
  submitting: PropTypes.bool,
  handleSubmit: PropTypes.func,
};

export default reduxForm({
  form: 'register-form',
  validate,
})(RegisterForm);
```



以下がrenderInput componentのソースです。propsを展開して、inputやrestを取り出していることに注目してください。そのままInput componentに展開されます。特に**(1)input object (redux-form) はvalueを含んでおり、(2)Input (antd component) のvalue属性の値としてそのまま適用されます。このvalueはactionによりRedux stateにbindされています。**またrestにはprefixが含まれており、そのままアイコン情報が展開されます。
[(1) Redux-form Fieldのinput object](https://qiita.com/sand/items/5ef1564546c130080398#%EF%BC%93-field-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
[(2) Antd Input component](https://ant.design/components/input/)


```JSX:src/input.js
import React from 'react';
import { Form, Input } from 'antd';

const newComponent = props => {
  const { input, meta, hasFeedback, label, ...rest } = props;
  const hasError = meta.touched && meta.invalid;

  return (
    <Form.Item
      label={label}
      help={hasError && meta.error}
      hasFeedback={hasFeedback && hasError}
      validateStatus={hasError ? 'error' : 'success'}
    >
      <Input {...input} {...rest} />
    </Form.Item>
  );
};

export default newComponent;
```


## 実行結果

redux-formのエラーメッセージ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/1ba4e9de-86ba-3a30-9419-db1946d9ca3b.png)


登録時の **redux-logger** のログ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/c0fe9331-9c76-1361-7a97-a219701e3767.png)



今回は以上です。