react-router で遷移先のコンポーネントに props を渡す

ネストしたルートに遷移するとき、 遷移先のルートに対応するコンポーネントが、親コンポーネントの props.children にセットされている。

ここで props.children をそのまま表示せずに、 React.cloneElement で props.children を複製すると、 その際に props を渡せる。

もっとスマートな方法が提供されていたらいいんだけど、 今のところこの方法しか無さそう。

何はともあれ、ルートコンポーネントのみに状態を持たせ、子コンポーネントはステートレスにできた。

var React = require("react");
var ReactDOM = require("react-dom");
var ReactRouter = require("react-router");
var Router = ReactRouter.Router;
var Link = ReactRouter.Link;
var Route = ReactRouter.Route;
var IndexRoute = ReactRouter.IndexRoute;

var User = React.createClass({
  // ID は URL に埋め込む。
  // クエリパラメータで名前を渡す。
  render: function() {
    return (
        <tr>
          <td>
            <Link to={"/users/" + this.props.user.id} query={{name: this.props.user.name}}>
              {this.props.user.name}
            </Link>
          </td>
        </tr>
    );
  }
});

var Users = React.createClass({
  getUsers: function() {
    return this.props.users.map(function(u) {
      return <User user={u} />;
    });
  },

  render: function() {
    return (
      <div className="users">
        <h1>ユーザー一覧</h1>
        <table>
          <thead>
            <tr>
              <th>名前</th>
            </tr>
          </thead>
          <tbody>
            {this.getUsers()}
          </tbody>
        </table>
      </div>
    );
  }
});

var UserDetail = React.createClass({
  render: function() {
    // パスに埋め込まれたパラメータは params から取得できる
    var userId = this.props.params.id;

    // ユーザーを取得
    var user = this.props.users[userId - 1];

    // 名前はクエリパラメータで受け取ったものを表示してみる
    return (
      <div className="user-detail">
        <h1>ユーザー詳細</h1>
        <dl>
          <dt>ID</dt>
          <dd>{user.id}</dd>
          <dt>名前</dt>
          <dd>{this.props.location.query.name}</dd>
          <dt>年齢</dt>
          <dd>{user.age}</dd>
        </dl>
      </div>
    );
  }
});

var About = React.createClass({
  render: function() {
    return (
      <div className="about">
        <h1>このサンプルについて</h1>
        <p>react-router のサンプルです。</p>
      </div>
    );
  }
});

var MyApp = React.createClass({
  getInitialState: function() {
    // ルートコンポーネントだけが状態を持つ
    return {
      users: [
        { id: 1, name: "香川", age: 26 },
        { id: 2, name: "本田", age: 29 },
        { id: 3, name: "長友", age: 29 },
        { id: 4, name: "岡崎", age: 29 },
        { id: 5, name: "内田", age: 28 },
        { id: 6, name: "清武", age: 25 }
      ]
    };
  },

  render: function() {
    // children の要素をクローンして描画する。
    // その際に props を渡す。
    return (
      <div>
        <ul>
          <li><Link to="/users">ユーザー一覧</Link></li>
          <li><Link to="/about">このサンプルについて</Link></li>
        </ul>
        <div>
          {this.props.children && React.cloneElement(this.props.children, {
            users: this.state.users
          })}
        </div>
      </div>
    );
  }
});

// Router や Route も React コンポーネント
ReactDOM.render((
  <Router>
    <Route path="/" component={MyApp}>
      <IndexRoute component={Users}/>
      <Route path="users" component={Users}/>
      <Route path="users/:id" component={UserDetail}/>
      <Route path="about" component={About}/>
    </Route>
  </Router>
  ),
  document.getElementById("content")
);