React.js で親コンポーネントと子コンポーネントが連携するシンプルなサンプルを書いてみた。

元ネタは、書籍 『入門React (Developing a React Edge)』 のサンプルコード bleeding-edge-sample-app/client/app/components/answers at master · backstopmedia/bleeding-edge-sample-app · GitHub で、ここから余計なものを省いてシンプルなコードにしたもの。

サンプルのスクリーンショット。
React.js sample: Parent Component and Child Components

あらかじめ、子コンポーネントの onChanged プロパティに、親コンポーネントのコールバック関数を設定しておいて、子コンポーネントのイベントが発生したら、子コンポーネントから this.props.onChanged を呼び出すことで、親コンポーネントのコールバック関数が呼び出される仕組み。

サンプルコード。


<!DOCTYPE html>
<html>
<head>
  <title>React.js sample: Parent Component and Child Components</title>
  <meta charset="UTF-8"> 
  <script src="http://fb.me/react-0.12.2.js"></script>
  <script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
</head>
<body>

<script type="text/jsx">
 
// 子コンポーネント (ラジオボタン)
var Child = React.createClass({
  propTypes: {
    id: React.PropTypes.string,
    name: React.PropTypes.string.isRequired,
    label: React.PropTypes.string.isRequired,
    value: React.PropTypes.string.isRequired,
    checked: React.PropTypes.bool
  },

  getDefaultProps: function(){
    return {
      checked: false
    };
  },

  getInitialState: function(){
    return {
      checked: !!this.props.checked
    };
  },

  componentWillReceiveProps: function(nextProps){
    console.log("Child#componentWillReceiveProps: " + nextProps.id);
    if(nextProps.checked !== undefined) {
      this.setState({
        checked: nextProps.checked
      });
    }
  },

  handleChanged: function(e){
    console.log("Child#handleChanged: " + this.props.value);
    var checked = e.target.checked;
    this.setState({checked: checked});
    if(checked){
      // 親コンポーネントのコールバック関数を呼び出す
      this.props.onChanged.apply(this.props, [this.props.value]);
    }
  },

  render: function(){
    console.log("Child#render: " + this.props.id);
    return (
      <div className="radio">
        <label>
          <input type="radio"
            name={this.props.name}
            id={this.props.id}
            value={this.props.value}
            checked={this.state.checked}
            onChange={this.handleChanged} />
          {this.props.label}
        </label>
      </div>
    );
  }
});
 
// 親コンポーネント
var Parent = React.createClass({
  propTypes: {
    value: React.PropTypes.string,
    choices: React.PropTypes.array.isRequired
  },
  
  getInitialState: function(){
    return {
      id: "multiple-choice",
      value: this.props.value
    };
  },

  // 子コンポーネントから呼び出すためのコールバック関数
  handleChanged: function(value){
    console.log("Parent#handleChanged: " + value);
    this.setState({value: value});
  },

  renderChoices: function(){
    console.log("Parent#renderChoices: " + this.state.id);
    var list = [];
    for(var i=0; i<this.props.choices.length; i++){
      var choice = this.props.choices[i];
      list.push(Child({
        id: "choice-" + i,
        name: this.state.id,
        label: choice,
        value: choice,
        checked: this.state.value === choice,
        onChanged: this.handleChanged // 子コンポーネントの onChanged プロパティにコールバック関数をセット
      }));
    }
    return list;
  },

  render: function(){
    console.log("Parent#render: " + this.state.value);
    return (
      <div className="form-group">
        <label className="survey-item-label" htmlFor={this.state.id}>{this.props.label}</label>
        <div className="survey-item-content">
            {this.renderChoices()}
        </div>
        <div>{this.state.value && this.state.value + "が選択されました"}</div>
      </div>
    );
  }
});

React.render(
  <Parent choices={["aaa","bbb","ccc"]}></Parent>,
  document.getElementById("example")
);
</script>

<div style="margin: 50px;">React.js sample: Parent Component and Child Components</div>
 
<div style="margin: 50px;" id="example"></div>

<div style="margin: 50px;">ref. <a href="https://github.com/backstopmedia/bleeding-edge-sample-app/tree/master/client/app/components/answers">bleeding-edge-sample-app/client/app/components/answers at master · backstopmedia/bleeding-edge-sample-app · GitHub</a></div>

</body>
</html>

tags: react.js javascript

Posted by NI-Lab. (@nilab)