LoginSignup
3
2

More than 3 years have passed since last update.

React UI library の antd について (3) - redux-form

Last updated at Posted at 2019-07-06

React UI library の antd について (1) - Button
React UI library の antd について (2) - Layout
React UI library の antd について (3) - redux-form


React のUI libraryであるantdは使い方はとても簡単でcomponentsも種類が多いのを見てきました。Layout componentも比較的簡単に使えました。

今回はFormとInput componentを見ていきます。

validateの方法は2つ示唆されています。最初はantd Formで標準のForm.createを使うことです。2番目はForm.createを使わずに行うことです。

2番目の方法としては、まったく手動で行うとかいろいろあるでしょうが、今回はredux-formを利用します。

Form.createの使い勝手は少し複雑な気がします。比べてredux-formを使う方法はシンプルでわかりやすいです。

Form.createを使った場合 - Antd標準

Antd Form component

antdのForm component とInput componentの使い方を見ていきます。ここではform の validateは、antd Formの標準のやり方ですが、Form.createでdecorateすること行います。

以下のコードは、HorizontalLoginForm という component を Form.create で decorate しています。

const WrappedHorizontalLoginForm = 
    Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);

Form.create で decorate された component には this.props.form property が渡されます。this.props.formはいくつかの属性を持ちます。以下にその中のいくつかを示します。(==> 全リストはこちら

  • validateFields
  • getFieldDecorator
  • getFieldError
  • getFieldsError
  • isFieldTouched

<Form.Item />の中では、getFieldDecoratorでinput controlをラップします。その中でvalidate ruleをまとめて記述していくことになります。一か所で全ての情報をかけるので、ある意味見やすいと言えます。

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'antd/dist/antd.css';
import { Form, Icon, Input, Button } from 'antd';

function hasErrors(fieldsError) {
  return Object.keys(fieldsError).some(field => fieldsError[field]);
}

class HorizontalLoginForm extends React.Component {
  componentDidMount() {
    // To disabled submit button at the beginning.
    this.props.form.validateFields();
  }

  handleSubmit = e => {
    e.preventDefault();
    this.props.form.validateFields((err, values) => {
      if (!err) {
        console.log('Received values of form: ', values);
      }
    });
  };

  render() {
    const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;

    // Only show error after a field is touched.
    const usernameError = isFieldTouched('username') && getFieldError('username');
    const passwordError = isFieldTouched('password') && getFieldError('password');
    return (
      <Form layout="inline" onSubmit={this.handleSubmit}>
        <Form.Item validateStatus={usernameError ? 'error' : ''} help={usernameError || ''}>
          {getFieldDecorator('username', {
            rules: [{ required: true, message: 'Please input your username!' }],
          })(
            <Input
              prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
              placeholder="Username"
            />,
          )}
        </Form.Item>
        <Form.Item validateStatus={passwordError ? 'error' : ''} help={passwordError || ''}>
          {getFieldDecorator('password', {
            rules: [{ required: true, message: 'Please input your Password!' }],
          })(
            <Input
              prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
              type="password"
              placeholder="Password"
            />,
          )}
        </Form.Item>
        <Form.Item>
          <Button type="primary" htmlType="submit" disabled={hasErrors(getFieldsError())}>
            Log in
          </Button>
        </Form.Item>
      </Form>
    );
  }
}

const WrappedHorizontalLoginForm = Form.create({ name: 'horizontal_login' })(HorizontalLoginForm);

ReactDOM.render(<WrappedHorizontalLoginForm />, document.getElementById('root'));

初期画面
image.png

一度入力後、削除するとエラーとなる
image.png

正しく入力後、ログインボタンを押す
image.png

コンソールにログが出力されている
image.png

redux-form を使った場合

【注意】(2019/07/13)

いろいろ試した結果、redux-formとreact-router v4の組み合わせがうまく動作しないという結論に達しました。reduxForm()とconnect()という2つのHOCで2重にラップした時に、propsが、最終的なコンポーネントにうまく伝わっていかない感じです。ラップする順番を変えてもだめでした。しかしreduxForm()の代わりに、antdのForm.create()でラップすればうまくいきました。何か私の見落としがあるのかもしれませんが、当面はForm.create()でいろいろ試していきたいと思います。

Form component
マニュアルには次のような記述があります。Form.create はformデータを集めvalidateします。しかしForm.createによるやり方が気に入らない場合は、使う必要はありません。手動でvalidateを行っても構いません。

ここではvalidateをredux-formで行いたいと思います。個人的にはこちらの方がシンプルで柔軟なような気がします。
redux-form (1) - Simple Form Example

つまりantdとredux-formを組み合わせて使うわけです。

必要なライブラリをインストールします。

yarn add redux react-redux redux-form

以下はantdとredux-formを同時に使用しているコードです。Form.createは使わず、代わりにreduxFormを使っていることに注意してください。

  • <Form />はantdです。
  • <Field />はredux-formです。

<Field /> の component={renderInput} に注目してください。renderInput は antd のcomponentで構成されたcomponentになります。renderInput には prefix等のpropを引き渡しています。

src/HorizontalLoginForm.js
import React from 'react';
import 'antd/dist/antd.css';
import { Form, Icon, Button } from 'antd';
import { Field, reduxForm } from 'redux-form';
import renderInput from './input';


const HorizontalLoginForm = props => {

  const { handleSubmit, pristine, submitting, message } = props

  return (
      <Form layout="inline" onSubmit={handleSubmit}>
        <Field
          hasFeedback
          type="text"
          name="name"
          component={renderInput}
          disabled={submitting}
          label="Name"
          placeholder="Full Name"
          prefix={<Icon type="user" style={{ color: 'rgba(0,0,0,.25)' }} />}
        />
        <Field
          hasFeedback
          type="password"
          name="password"
          component={renderInput}
          disabled={submitting}
          label="Password"
          placeholder="Password"
          prefix={<Icon type="lock" style={{ color: 'rgba(0,0,0,.25)' }} />}
        />
        <Form.Item>
          <Button type="primary" htmlType="submit" className="btn-submit" disabled={pristine || submitting}>
            Log in
          </Button>
        </Form.Item>
        {!!message && <p className="caption-invalid">{message}</p>}
      </Form>
  );
}

const validate = values => {
  const errors = {};

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

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

  return errors;
};


export default reduxForm({
  form: 'login-form',
  validate,
})(HorizontalLoginForm);

以下が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;

ここで分割代入 (Destructuring assignment) 構文を思い出しましょう。

({a, b, ...rest} = {a: 10, b: 20, c: 30, d: 40});
console.log(a); // 10
console.log(b); // 20
console.log(rest); // {c: 30, d: 40}

以下のindex.jsでは主にredux-formの設定を行っています。Redux storeの設定や、onSubmit関数の定義を行っています。

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'
import HorizontalLoginForm from './HorizontalLoginForm'

const dest = document.getElementById('root')
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 HorizontalLoginForm = require('./HorizontalLoginForm').default
  ReactDOM.hydrate(
    <Provider store={store}>
      <h2>Form</h2>
      <HorizontalLoginForm onSubmit={showResults} />
    </Provider>,
    dest
  )
}

render()

初期画面。antdのアイコンが表示されています。
image.png

入力を消すとエラーが表示されます。ボタンは無効化されます。
image.png

入力を正しく行うと、ボタンも有効化されます。
image.png

ボタンを押すと、onSubmit関数が起動します。
image.png

今回は以上です。

3
2
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
3
2