ReactとMaterial-UIを使ってフォームを作るときにどのようにControlled Componentを実現するかで少し悩んだので、ご参考までに自分なりの解決策をご紹介します。
- 対象バージョン:material-ui: 0.18.6
理想の実装方法
通常のHTMLの場合と同様に下記のように実装できると理想的です。
- JSX
<input name="number" type="number" value={this.state.value} onChange={this.handleChange} />
- イベントハンドラ
handleChange(event) {
this.setState({value: event.target.value});
}
ところがMaterial-UIのコンポーネントではいくつかの問題が発生します。
1. SelectFieldではイベントハンドラのevent.targetにMenuItemが設定される
event.targetには選択したMenuItem(実際にはMenuItemが変換されたdiv要素)が指定されます。
このため、SelectFieldのname属性に他のコンポーネントと同じようにアクセスできません。
- 参考
2. イベントハンドラのコールバック関数の形式がバラバラである
コンポーネント | コールバック関数の形式 |
---|---|
TextField | function(event: object, newValue: string) => void |
SelectField | function(event: object, key: number, payload: any) => void |
DatePicker | function(null: undefined, date: object) => void |
3. イベントハンドラを登録する属性名がバラバラである
コンポーネント | 属性名 |
---|---|
Checkbox | onCheck |
RadioButton | onChange |
Toggle | onToggle |
TextField | onChange |
SelectFiled | onChange |
DatePicker | onChange |
TimePicker | onChange |
4. id属性もしくはname属性を設定しないと警告メッセージが表示されるコンポーネントがある
RadioButton、TextField、Timepickerにid属性もしくはname属性を設定しないと下記の警告メッセージが表示されます。
Warning: Material-UI: We don't have enough information to build a robust unique id for the TextField component. Please provide an id or a name.
これは既知のバグのようで、現在のところは特に設定する理由がなくても属性を設定しておくのが良さそうです。
参考:https://github.com/callemall/material-ui/issues/4659
解決策
色々と悩んだ結果、イベントハンドラで直接stateを変更するのではなく、フォームの名前を渡してstateを変更する関数を返す、という実装で上記の問題を回避しました。
- JSX
<TextField
name="tf"
value={this.state.tf}
onChange={this.handleChange('tf')}
/>
- イベントハンドラ
handleChange(name) {
var _this = this;
return function(event, a, b) {
if(typeof b === 'undefined') {
_this.setState({[name]: a});
} else {
_this.setState({[name]: b});
}
}
}