試行錯誤の末に、
react-bootstrap が提供する FormGroup
や FormControl
といったフォーム関連コンポーネントを、
redux-form と一緒に使うことができた。
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { createStore, combineReducers } from 'redux'; import { Provider, connect } from 'react-redux'; import { Field, reduxForm, reducer as formReducer } from 'redux-form'; import { Grid, Row, Col, Table, FormGroup, ControlLabel, FormControl } from 'react-bootstrap'; // bootstrap の CSS を読み込む。 // webpack で CSS Module を使っていることが前提。 import '../node_modules/bootstrap/dist/css/bootstrap.css'; // カスタマーを追加するアクション const ADD_CUSTOMER = "ADD_CUSTOMER"; // カスタマーを追加するときに使う Action Creator function addCustomer(customer) { return { type: ADD_CUSTOMER, customer }; } // 初期状態 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_CUSTOMER: return Object.assign({}, state, { customers: [...state.customers, action.customer] }); default: return state; } }; // redux-form が提供する reducer も使う const rootReducer = combineReducers({ customers: customersReducer, form: formReducer }); // アプリ唯一のストアを作成 const store = createStore(rootReducer); // redux-form 用バリデーション関数 const validate = (values) => { // 検証に失敗したら values と同じキーで // エラーメッセージをセットする。 const errors = {}; if (!values.code) { errors.code = "コードを入力してください。"; } if (!values.name) { errors.name = "名前を入力してください。"; } if (!values.kana) { errors.kana = "カナを入力してください。"; } return errors; }; // フォームの入力用コンポーネント。 // react-bootstrap のフォーム関連コンポーネントを使う。 class FormField extends Component { render() { // input と meta は redux-form が渡してくる props。 // type や label は Field に渡した props がそのまま渡ってくる。 // select や textarea に対応するために componentClass を受け取る。 // select の option に対応するために children も受け取る。 const { input, label, type, componentClass, children, meta: { touched, error } } = this.props; return ( <FormGroup> <ControlLabel> {label} </ControlLabel> <FormControl {...input} type={type || "text"} componentClass={componentClass || "input"} > {children} </FormControl> {touched && error && <span className="text-danger">{error}</span>} </FormGroup> ); } } // カスタマー登録フォーム本体 class _CustomerForm extends Component { render() { const { handleSubmit, pristine, reset, submitting } = this.props; return ( <form onSubmit={handleSubmit}> <Field name="code" component={FormField} type="text" label="コード"/> <Field name="name" component={FormField} type="text" label="名前"/> <Field name="kana" component={FormField} type="text" label="カナ"/> <div className="form-group"> <button className="btn btn-primary" type="submit" disabled={submitting}> 追加 </button> <button className="btn btn-default" type="button" onClick={reset} disabled={submitting || pristine}> クリア </button> </div> </form> ); } } // カスタマー登録フォームを redux-form 化。 // form にはユニークな文字列を指定する。 const CustomerForm = reduxForm({ form: "customerForm", validate })(_CustomerForm); // さらに connect でコンテナコンポーネント化。 // redux-form でラップしたコンポーネントには onSubmit の props が // 生えている。 // submit 時の処理はコンテナコンポーネントで実装する。 const CustomerFormContainer = connect( state => { return {}; }, dispatch => { return { onSubmit: (values) => { dispatch(addCustomer(values)); } }; } )(CustomerForm); // カスタマー一覧を表示するコンポーネント 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}> <CustomerFormContainer /> <CustomerListContainer /> </Col> </Row> </Grid> ); } } ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById("app") );
redux-form の Field
は自身に渡された props を、component
に指定したコンポーネントにほとんど渡してくれる。なので FormControl
が必要とするものを Field
に渡せばいいって寸法。
ライブラリのソースコードを読む習慣は大事だね。