React+Reduxでフォーム画面を作る時にRedux Form
というライブラリが便利だと聞いたので実際に使ってみました。
redux-form
Redux Formはテキストボックスなどに入力された値を即座にstoreにdispatchしてくれるライブラリのようです。
ライブラリの導入
Redux Formを使うには当然ですがreduxが必要です。
まずは必要となるライブラリをインストールしましょう
$ npm install redux react-redux redux-form redux-logger
redux-loggerは別に無くても問題なく動作しますが、actionが呼び出される時にconsoleにログを表示してくれるのでデバッグに便利です。
次にindexを書き換えます。redux-loggerはミドルウェアのひとつなのでapplyMiddlewareが必要になります。
import React from 'react';
import ReactDOM from 'react-dom';
import logger from 'redux-logger';
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux';
import reducer from './reducer';
import App from './App';
const store = createStore(
reducer,
applyMiddleware(logger)
);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root'));
Reducerを作成
Redux Formの導入はreducer内に記載します。
import { combineReducers } from "redux";
import { reducer as formReducer } from "redux-form";
const reducer = combineReducers({
form: formReducer
});
export default reducer;
書くのはこれだけ。actionも書く必要ないっぽいです。便利。
コンポーネントの作成
いよいよコンポーネントを書いていきます。
import React, { Component } from "react";
import { Field, reduxForm } from "redux-form";
import Form from "./Form";
import { connect } from "react-redux";
class App extends Component {
showText() {
window.alert(this.props.text.values.test);
}
render() {
return (
<div>
<Field name="test" type="text" component={Form} />
<button type="button" onClick={this.showText.bind(this)}>
テキスト表示
</button>
</div>
);
}
}
const mapStateToProps = state => ({
text: state.form.text
});
export default connect(mapStateToProps)(
reduxForm({
form: "text",
enableReinitialize: true
})(App)
);
import React, { Component } from "react";
export default class Form extends Component {
render() {
const { input, name, type } = this.props;
const disabled = false;
return (
<div>
<input {...input} name={name} type={type} disabled={disabled} />
</div>
);
}
}
App.jsをRedux Formとconnectし、Redux Formを利用したテキストボックスとアラートを呼び出す用のボタンを作りました。
Form.jsにテキストボックスのコードを記載し、App.jsでそれを呼び出して使用しています。
フォームの種類によって場合分けしたいときは以下のようにForm.js内で条件分岐させることも可能です。
import React, { Component } from "react";
export default class Form extends Component {
render() {
const { input, name, type } = this.props;
const disabled = false;
return (
<div>
{type === "text" && (
<input {...input} name={name} type={type} disabled={disabled} />
)}
{type === "select" && (
<select {...input} name={name} type={type}>
<option value={"one"}>ひとつめ</option>
<option value={"two"}>ふたつめ</option>
</select>
)}
</div>
);
}
}
テキストボックスに入力された値をその場ですぐ表示させるためには以下のように書く必要があるみたいです。
import React, { Component } from "react";
import { Field, reduxForm } from "redux-form";
import Form from "./FormItem";
import { connect } from "react-redux";
class App extends Component {
render() {
return (
<div>
<Field name="test" type="text" component={Form} />
{this.props.text && this.props.text.values && (
<p>{this.props.text.values.test}</p>
)}
</div>
);
}
}
const mapStateToProps = state => ({
text: state.form.text
});
export default connect(mapStateToProps)(
reduxForm({
form: "text",
enableReinitialize: true
})(App)
);
&&を使って条件分岐している理由は、テキストボックスに何も入力されていない状態だとtextとvaluesがundefinedになってしまうせいでエラーになってしまうからです。
もう少し詳しくRedux Formについて調べていけばもっとスマートに書ける方法が見つかる気がする…