react + reduxでフォームを作成する際、redux-formを使用しましたが、情報が少なかったので個人的にまとめてみました。
#実装
まずreducerを作成します。
import { createStore, combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
const reducer = combineReducers({
form: formReducer
});
const store = createStore(reducer)
export default reducer;
コンポーネントを作成する。
import React from "react";
import Form from "./Form";
export default class App extends Component {
render() {
return (
<div>
<Form onSubmit={showResult} />
</div>
);
}
}
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が指定可能です。
自身で作成したコンポーネントの指定もできます。
##フォームを追加可能にする
フォームを追加し、複数のデータを一括で保存できるようにしていきます。
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コンポーネントの配列を表す名称になります。
##初期値を設定
初期値の設定とデータの送信に成功した際に初期化する処理を実装します。
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
を使い上記のような記述で実装できます。
##バリデーションをかける
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だと以下のようになる
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>
);
}
}
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)