LoginSignup
2
5

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-07-10

redux-form (1) - Simple Form Example
redux-form (2) - Synchronous Validation Example
redux-form (3) - Field-Level Validation Example
redux-form (4) - Submit Validation Example
redux-form (5) - Initialize From State
redux-form (6) - ユーザ登録


ReactでForm componentを作るときに、とても便利なredux-formの説明です。

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

redux-form (1) - Simple Form Example

今回は少し実用を意識して、ユーザ登録を実装します。UIコンポネントを作成するためにantdを使います。
React UI library の antd について (1) - Button

ユーザ登録

画面イメージ

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

image.png

環境設定

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

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

念のため、package.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 を定義します。ユーザを追加するだけの簡単なものにします。

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だけを定義します。

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

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

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の大枠はantdFormを利用しています。
  • Formの子要素にはredux-formField を利用しています。
  • 更にFieldのcomponent属性にantdInput(で作られたcomponent)を利用しています。

antdとredux-formを組み合わせについては、以下の記事も参照ください。
React UI library の antd について (3) - redux-form

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

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
(2) Antd Input component

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

登録時の redux-logger のログ
image.png

今回は以上です。

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5