unhurried

コンピュータ関連ネタがほとんど、ときどき趣味も…

ReactとMaterial-UIでControlled Componentを実現する

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});
        }
    }
}