redux-form の FieldArray を使えば、 複数のデータを同時に編集して一括登録、 みたいなことができる。 表形式入力的なフォーム。 DataGrid もどき。
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { createStore, combineReducers } from 'redux'; import { Provider, connect } from 'react-redux'; import { Field, FieldArray, reduxForm, reducer as formReducer } from 'redux-form'; import { Grid, Row, Col, Button, Table, FormGroup, ControlLabel, FormControl } from 'react-bootstrap'; // bootstrap の CSS を読み込む。 // webpack で CSS Module を使っていることが前提。 import '../node_modules/bootstrap/dist/css/bootstrap.css'; // カスタマーを複数追加するアクション const ADD_CUSTOMERS = "ADD_CUSTOMERS"; // カスタマーを複数追加するときに使う Action Creator function addCustomers(customers) { return { type: ADD_CUSTOMERS, customers }; } // 初期状態 const initialState = { customers: [ { code: "10", name: "香川", kana: "カガワ" }, { code: "4", name: "本田", kana: "ホンダ" }, { code: "5", name: "長友", kana: "ナガトモ" }, { code: "9", name: "岡崎", kana: "オカザキ" } ] }; // カスタマーを操作する Reducer const customersReducer = (state = initialState, action) => { switch (action.type) { case ADD_CUSTOMERS: return Object.assign({}, state, { customers: [...state.customers, ...action.customers] }); default: return state; } }; // redux-form が提供する reducer も使う const rootReducer = combineReducers({ customers: customersReducer, form: formReducer }); // アプリ唯一のストアを作成 const store = createStore(rootReducer); // redux-form 用バリデーション関数 // customers の中身を forEach で回して 1 つずつチェックする。 const validate = (values) => { const errors = {}; if (!values.customers || values.customers.length == 0) { errors.customers = { _error: "カスタマーを入力してください。" }; } else { const customersArrayErrors = []; values.customers.forEach((customer, index) => { const customerErros = {}; if (customer && !customer.code) { customerErros.code = "コードを入力してください。"; } if (customer && !customer.name) { customerErros.name = "名前を入力してください。"; } if (customer && !customer.kana) { customerErros.kana = "カナを入力してください。"; } customersArrayErrors[index] = customerErros; }); if (0 < customersArrayErrors.length) { errors.customers = customersArrayErrors; } } return errors; }; // フォーム入力用コンポーネント const renderField = ({ input, type, meta: { touched, error } }) => ( <td> <FormControl {...input} type={type}/> {touched && error && <span className="text-danger">{error}</span>} </td> ); // カスタマー表形式入力用コンポーネント // FieldArray の component に渡したコンポーネントには // props に fileds が渡ってくる。 const renderCustomers = ({ fields, meta: { touched, error } }) => ( <Table> <thead> <tr> <th>コード</th> <th>名前</th> <th>カナ</th> </tr> </thead> <tbody> {fields.map((customer, index) => <tr key={index}> <Field name={`${customer}.code`} type="text" component={renderField}/> <Field name={`${customer}.name`} type="text" component={renderField}/> <Field name={`${customer}.kana`} type="text" component={renderField}/> </tr> )} { touched && error && <tr> <td><span className="text-danger">{error}</span></td> <td></td> <td></td> </tr> } <tr> <td> <Button type="button" onClick={() => fields.push()}> 行追加 </Button> </td> <td></td> <td></td> </tr> </tbody> </Table> ); // カスタマー登録フォーム本体 class _CustomerTableForm extends Component { render() { const { handleSubmit, pristine, reset, submitting } = this.props; return ( <form onSubmit={handleSubmit}> <FieldArray name="customers" component={renderCustomers} /> <FormGroup> <Button bsStyle="primary" type="submit" disabled={submitting}> 保存 </Button> <Button type="button" disabled={pristine || submitting} onClick={reset}> リセット </Button> </FormGroup> </form> ); } } // カスタマー登録フォームを redux-form 化。 // form にはユニークな文字列を指定する。 const CustomerTableForm = reduxForm({ form: "customerForm", validate })(_CustomerTableForm); // さらに connect でコンテナコンポーネント化。 // redux-form でラップしたコンポーネントには onSubmit の props が // 生えている。 // submit 時の処理はコンテナコンポーネントで実装する。 const CustomerTableFormContainer = connect( state => { return {}; }, dispatch => { return { onSubmit: (values) => { dispatch(addCustomers(values.customers)); } }; } )(CustomerTableForm); // カスタマー一覧を表示するコンポーネント class CustomerList extends Component { renderCustomers() { return this.props.customers.map(c => <tr key={c.code}> <td>{c.code}</td> <td>{c.name}</td> <td>{c.kana}</td> </tr> ); } render() { return ( <Table> <thead> <tr> <th>コード</th> <th>名前</th> <th>カナ</th> </tr> </thead> <tbody> {this.renderCustomers()} </tbody> </Table> ); } } const CustomerListContainer = connect( state => { return { customers: state.customers.customers }; } )(CustomerList); class App extends Component { render() { return ( <Grid> <Row> <Col md={12}> <CustomerTableFormContainer /> <CustomerListContainer /> </Col> </Row> </Grid> ); } } ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("app") );
だんだん、バリデーションのための関数を書くのがメンドクなってきた。