hina2go

主に技術系のこことか。最近React始めました。

ReactでGithubのリポジトリブラウザを作ってみる (1)

GithubRepositories APIは、指定したユーザーのリポジトリを取得することができます。

$ curl -s 'https://api.github.com/users/toshiyukihina/repos'
[
  {
    "id": 38911425,
    "name": ".emacs.d",
    "full_name": "toshiyukihina/.emacs.d",
    "owner": {
      "login": "toshiyukihina",
      "id": 1883527,
      "avatar_url": "https://avatars.githubusercontent.com/u/1883527?v=3",
      "gravatar_id": "",
      "url": "https://api.github.com/users/toshiyukihina",
      "html_url": "https://github.com/toshiyukihina",
      "followers_url": "https://api.github.com/users/toshiyukihina/followers",
      "following_url": "https://api.github.com/users/toshiyukihina/following{/other_user}",
      "gists_url": "https://api.github.com/users/toshiyukihina/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/toshiyukihina/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/toshiyukihina/subscriptions",

... (snip) ...

このAPIを使って、指定したユーザーのリポジトリ一覧を取得するアプリを作ってみます。

イメージはこんな感じです。

f:id:hinathy:20160428143709p:plain

コンポーネントの構成は以下。

  • App
    • SearchBox
    • RepositoryList
      • Repository

準備

ここからプロジェクトの雛形をダウンロードして、bundle install --path vendor/bundle && npm installしておきます。

コンポーネントのファイルを生成する

$ for c in app search_box repository_list repository; do bundle exec rails g react:component $c; done

Repositoryを実装する

Repositoryは、以下をパラメータとしてとります。

  • name
  • description
import React from 'react';

class Repository extends React.Component {

  render() {
    return (
      <tr>
        <td>{this.props.name}</td>
        <td>{this.props.description}</td>
      </tr>
    );
  }
  
}

Repository.PropTypes = {
  name: React.PropTypes.string.isRequired,
  description: React.PropTypes.string.isRequired
};

export default Repository;

RepositoryListRepositoryレンダリングする

Repositoryに渡すデータは、RepositoryListの上位コンポーネントAppからrepositoriesというPropで渡されることにします。

import React from 'react';
import Repository from './repository';

class RepositoryList extends React.Component {

  render() {
    const repositoryNodes = this.props.repositories.map((repository) => {
      return (
        <Repository
            key={repository.id}
            name={repository.name}
            description={repository.description}
        />
      );
    });

    return (
      <div>
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            {repositoryNodes}
          </tbody>
        </table>
      </div>
    );
  }
  
}

RepositoryList.PropTypes = {
  repositories: React.PropTypes.arrayOf(React.PropTypes.object)
};

export default RepositoryList;

AppRepositoryListレンダリングする

RepositoryListに渡すデータは、AppGithub API経由で取得したデータを渡すことを想定します。 ですので、Appの初期ステートとして固定データを与えておきます。

import React from 'react';
import SearchBox from './search_box';
import RepositoryList from './repository_list';

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      repositories: [
        { id: '1', name: 'angular-ui-router', description: 'A sample program using angular-ui-router.' },
        { id: '2', name: 'angular-chat', description: 'A chat program using Angular.js.' },
        { id: '3', name: 'hello-redux', description: 'My first redux.' },
        { id: '4', name: 'dotfiles', description: 'My dotfiles.' }
      ]
    };
  }

  render() {
    return (
      <div>
        <SearchBox />
        <RepositoryList repositories={this.state.repositories} />
      </div>
    );
  }
  
}

export default App;

ここまでの実装で、以下のような画面になりました。

f:id:hinathy:20160428130031p:plain

SearchBoxの実装

SearchBoxは、入力されたGithubユーザー名を上位コンポーネントであるAppにコールバックします。 ですので、Propとして渡されたonSubmitを、Updateボタンクリック時に呼び出します。

import React from 'react';

class SearchBox extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      username: ''
    };

    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  handleSubmit(e) {
    e.preventDefault();

    this.props.onSubmit(this.state.username);
  }

  handleChange(e) {
    this.setState({username: e.target.value});
  }

  render() {
    return (
      <div>
        <form onSubmit={this.handleSubmit}>
          <input type="text" onChange={this.handleChange} placeholder="Enter username" />
          <input type="submit" value="Update" />
        </form>
      </div>
    );
  }
  
}

SearchBox.PropTypes = {
  onSubmit: React.PropTypes.func.isRequired
};

export default SearchBox;

AppSearchBoxを組み込む

render()で、SearchBoxレンダリングします。 同時に、SearchBoxのUpdateボタンがクリックされた時に呼び出してもらうhandleSubmit()をPropで渡しています。

HTTPクライアントは、superagentを使っていますので、npm install superagent --saveしています。

あと、初期ステートとしてthis.state.repositoriesに与えていたダミーデータは、空の配列にしています。

import React from 'react';
import SearchBox from './search_box';
import RepositoryList from './repository_list';
import request from 'superagent';

class App extends React.Component {

  constructor(props) {
    super(props);

    this.state = {
      repositories: []
    };

    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit(username) {
    request.get(`https://api.github.com/users/${username}/repos`)
           .end((err, res) => {
             if (res.ok) {
               this.setState({repositories: res.body});
             } else {
               console.error(err);
             }
           });
  }

  render() {
    return (
      <div>
        <SearchBox onSubmit={this.handleSubmit} />
        <RepositoryList repositories={this.state.repositories} />
      </div>
    );
  }
  
}

export default App;

スクリーンキャプチャ

こんな感じです。

gyazo.com

ソースコード

ここまでのコードは、release/0.0.1としてコミットしています。

github.com