LoginSignup
6
10

More than 3 years have passed since last update.

redux-form でフォームを作成する

Last updated at Posted at 2019-12-15

react + reduxでフォームを作成する際、redux-formを使用しましたが、情報が少なかったので個人的にまとめてみました。

実装

まずreducerを作成します。

Reducer.js
import { createStore, combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";

const reducer = combineReducers({
  form: formReducer
});

const store = createStore(reducer)
export default reducer;

コンポーネントを作成する。

App.js
import React from "react";
import Form from "./Form";

export default class App extends Component {
  render() {
    return (
      <div>
        <Form onSubmit={showResult} />
      </div>
    );
  }
}
Form.js
import React from "react";
import { Field, reduxForm } from "redux-form";

const Form = props => {
  const { handleSubmit } = props
  return (
    <form onSubmit={handleSubmit}>
      <Field
        name="FirstName"
        type="text"
        component="input"
      />
      <Field
        name="LastName"
        type="text"
        component="input"
      />
      <Field
        name="Comment"
        component="textarea"
      />
      <button type="submit">
        Submit
      </button>
    </form>
  )
}
export default reduxForm({
  form: 'simple' // a unique identifier for this form
})(Form)

reduxForm
フォームコンポーネントをラップし、入力をRedux actionにbindします。

Field コンポーネント
Redux のストアに、個々の入力を接続するものです。

属性がいくつかありますが、必須なのは2つです。
1つは name属性。フォーム値の値に対応する文字列パスです。
もう1つは component属性。
指定の方法はいくつかあり、上記のように文字列指定ではテキストボックスが表示されます。type属性にはHTMLで定義されているtypeが指定可能です。
自身で作成したコンポーネントの指定もできます。

フォームを追加可能にする

フォームを追加し、複数のデータを一括で保存できるようにしていきます。

Form.js
import { Field, reduxForm, FieldArray } from "redux-form";

const renderMembers = ({ fields }) => (
  <ul>
    <button 
      type="button" 
      onClick={() => fields.push({})}
    >
      追加
    </button>
    {fields.map((member, index) => (
      <li key={index}>
        <Field
          name={`${member}.FirstName`}
          type="text"
          component="input"
        />
        <Field
          name={`${member}.LastName`}
          type="text"
          component="input"
        />
        <Field
          name={`${member}.Comment`}
          component="textarea"
        />
      </li>
      <button type="submit">
        Submit
      </button>
    ))}
  </ul>
);

const Form = props => {
  const { handleSubmit } = props
  return (
    <form onSubmit={handleSubmit}>
      <FieldArray name="members" component={renderMembers} />
    </form>
  )
}

// 省略

同一のフォームの配列は、FieldArray コンポーネントを使って実現できます。
name属性は指定されたコンポーネントで記述されるFieldコンポーネントの配列を表す名称になります。

初期値を設定

初期値の設定とデータの送信に成功した際に初期化する処理を実装します。

Form.js
import { Field, reduxForm, FieldArray, reset } from "redux-form";

const renderMembers = ({ fields }) => (
  <ul>
    <button 
      type="button" 
      onClick={() => fields.push({
        comment: "デフォルト値です。"
      })}
    >
      追加
    </button>
    // 省略
  </ul>
);

// 省略

const afterSubmit = (result: any, dispatch: any) =>
  dispatch(reset('Form'));

export default reduxForm({
  form: 'Form',
  initialValues: {
    members: [
      {
        comment: "デフォルト値です。"
      }
    ]
  },
  onSubmitSuccess: afterSubmit,
})(Form)

reduxFormのパラメータとしてinitialValuesで初期値を指定します。
ただこれだと追加したフォームには初期値が反映されないので、fields.pushでも初期値を指定します。
これで初期値設定は完了です。

初期化には、redux-formで用意されているreset関数を使用します。送信が成功した際の処理は、onSubmitSuccessを使い上記のような記述で実装できます。

バリデーションをかける

Form.js
const validation = values => {
  const errors = {}
  if (!values.members || !values.members.length) {
    errors.members = { _error: 'メンバーを入力してください。' }
  } else {
    const membersArrayErrors = [];
    values.members.forEach((member, memberIndex) => {
      const memberErrors = {};
      if (member && !member.name) {
        memberErrors.firstName = "苗字を入力してください。";
      }
      membersArrayErrors[memberIndex] = memberErrors
    })
  }
  return errors
}

// 省略

export default reduxForm({
  form: 'Form',
  initialValues: {
    members: [
      {
        comment: "デフォルト値です。"
      }
    ]
  },
  onSubmitSuccess: afterSubmit,
  validate: validation,
})(Form)

reduxFormのパラメータとしてvalidateで指定します。

TypeScriptでの記述

TepeScriptだと以下のようになる

App.tsx
import React from "react";
import Form from "./Form";

export default class App extends Component<
  InjectedFormProps<{}, Props> & Props, State
  >  {
  render() {
    return (
      <div>
        <Form onSubmit={showResult} />
      </div>
    );
  }
}
Form.tsx
const renderMembers = ({fields}: any) => (
  <ul>
    <button 
      type="button" 
      onClick={() => fields.push({})}
    >
      追加
    </button>
    {fields.map((records: any, index: number) => (
      <li key={index}>
        <Field
          name={`${member}.FirstName`}
          type="text"
          component="input"
        />
        <Field
          name={`${member}.LastName`}
          type="text"
          component="input"
        />
        <Field
          name={`${member}.Comment`}
          component="textarea"
        />
      </li>
      <button type="submit">
        Submit
      </button>
    ))}
  </ul>
);

const Form = (props: any) => {
  const { handleSubmit } = props
  return (
    <form onSubmit={handleSubmit}>
      <FieldArray name="members" component={renderMembers} />
    </form>
  )
}

const validation = (values: any) => {
  const errors = {}
  if (!values.members || !values.members.length) {
    errors.members = { _error: 'メンバーを入力してください。' }
  } else {
    const membersArrayErrors = [];
    values.members.forEach((member, memberIndex) => {
      const memberErrors = {};
      if (member && !member.name) {
        memberErrors.firstName = "苗字を入力してください。";
      }
      membersArrayErrors[memberIndex] = memberErrors
    })
  }
  return errors
}

export default reduxForm({
  form: 'Form',
  initialValues: {
    members: [
      {
        comment: "デフォルト値です。"
      }
    ]
  },
  validate: validation,
})(Form)
6
10
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
6
10