ReactでTwichのランキングサイトを作ってみよう！

# はじめに
みなさん[Twitch](https://www.twitch.tv/)というゲームに特化した配信サイトはご存知でしょうか。Justin.tvからゲーム専用配信サイトとして派生し、現在はAmazon傘下にあるサイトです。今回はTwitchAPIとReactでランキングサイトを作成していこうと思います！

# ゴール
以下のようなランキングサイトを作成していきたいと思います！！
※ 実際に動いているものがみたい方は[こちら](https://twitch-ranking.herokuapp.com/)からご覧になれます。

![スクリーンショット 2018-12-02 17.45.22.png](https://qiita-image-store.s3.amazonaws.com/0/81341/4cecd50d-4352-3ce5-6d2b-716331c7267f.png)

# TwtichAPIキーの取得
[こちら](https://glass.twitch.tv/console/apps)からAPIキーの取得を行なってください。
ログインを行い下記の画像の赤枠の部分で囲んである**「アプリケーション登録」**から登録を行なってください。
![touroku.png](https://qiita-image-store.s3.amazonaws.com/0/81341/e872a2a6-e8a8-278b-477a-ef8b58fa3fc1.png)

名前は使用されていると使えないため、日付など入れて良しなにしましょう。リダイレクト先はローカル環境を指定することが可能のため`localalhost:3000`にしました。下記の画像の赤枠で囲まれている部分が**クライアントID**になります。以後使うため大切に保管してください。

![スクリーンショット 2018-11-28 15.24.22.png](https://qiita-image-store.s3.amazonaws.com/0/81341/f38eb58e-22a2-775d-ff18-459959047e0c.png)

# create-react-app
今回は`create-react-app`を使って雛形を作り、開発して行こうと思います。
※ `create-react-app`が使えない方は`npm install -g create-react-app`でインストールしてください。

```.sh
# create-react-app プロジェクト名
$ create-react-app twitch_ranking
```
`create-react-app`コマンドでアプリの雛形の作成が完了したら、作成したディレクトリに移動し以下のコマンドを実行してください。（`--save`オプションはpackage.jsonのdependenciesに追加するために行なっています。）`prop-types`はpropsの型チェックする際に使います。`@material-ui/core`はReactでマテリアルデザインを扱うためのUIコンポーネントです。今回はデザイン全般を[@material-ui/core](https://material-ui.com/)を使って実装します。`@material-ui/icons`アイコンを扱うUIコンポーネントです。使い勝手の良いアイコンがたくさんあるため今回もユーザーアイコンなど様々なアイコンを使います。

```.sh
# prop-typesコンポーネントのインストール
$ npm install --save prop-types
# @material-ui/coreコンポーネントのインストール
$ npm install --save @material-ui/core
# @material-ui/iconsコンポーネントのインストール
$ npm install --save @material-ui/icons
```

# Headerの作成
サイトのヘッダーを作成していきます。`プロジェクト名/src`配下に`Header.js`ファイルを作成してください。サイトのヘッダーはシンプルにタイトルのみの表示となります。ヘッダーのデザインは[公式のAppBar](https://material-ui.com/demos/app-bar/)を参考にしています。

```src/Header.js
import React from 'react';
import PropTypes from 'prop-types';
import {withStyles} from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';

const styles = {
  root: {
    width: '100%',
    backgroundColor: '#6441a5',
  },
};

const Header = (props) => {
  const {classes} = props;
  return (
    <AppBar position="static" className={classes.root}>
      <Toolbar>
        <Typography variant="title" style={{color: '#E6E6E6'}}>Twitchランキング（JP）</Typography>
      </Toolbar>
    </AppBar>
  );
};

Header.propTypes = {
  // classesがobjectであるかチェック 
  classes: PropTypes.object.isRequired,
};

export default withStyles(styles)(Header);
```

# ランキングページの作成
次にランキングページの作成をします。`プロジェクト名/src/App.js`を以下のようにしてください。ここでは`fetch`を用いてTwichAPIにGETリクエストを行なっています。`header`の`Client-ID`部分に先ほど取得したクライアントIDを入力してください。jsonを受け取る際にあらかじめ取得したjsonを格納する空の配列である`resp`をstateで用意します。Streamsコンポーネントでは取得した`resp`内に格納されている配列をmap関数を用いて処理しています。最後に先ほど作成したヘッダーと組み合わせてあげればページの完成です。

```src/App.js
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Header from './Header'

import Paper from '@material-ui/core/Paper'
import Typography from '@material-ui/core/Typography'
import GameIcon from '@material-ui/icons/Gamepad'
import ViewerIcon from '@material-ui/icons/Person'
// 後ほど作成する
import './twitch.css'


class TwitchRankPage extends Component {
  constructor(props) {
    super(props);
    this.state = {
      loading: true,
      resp: [],
    };
    // 取得件数を50に設定する。
    this.limit = 50
  }

  componentDidMount() {
    fetch(`https://api.twitch.tv/kraken/streams/?language=ja&limit=${this.limit}`, {
      method: 'get',
      headers: new Headers({
        'Accept': 'application/vnd.twitchtv.v5+json',
        'Client-ID': '先ほど取得したクライアントID',
      }),
    })
      .then((response) => response.json())
      .then((resp) => {
        this.setState({
          resp: resp.streams,
          loading: false,
        })
      })
      .catch((error) => {
        console.log("error", error)
      })
  }

  render() {
    const { resp } = this.state;
    return (
      <div>
        <Header />
        <Streams resp={resp} />
      </div>
    )
  }
}


const Streams = (props) => {
  const json = props.resp;
  let i = 1;

  return (
    <Paper className="paper">
      <table>
        <thead className="table-head">
          <tr>
            <th className="rank"><Typography variant={"subheading"}>順位</Typography></th>
            <th className="thumb"><Typography variant={"subheading"}>チャンネル</Typography></th>
            <th className="movie-title" />
            <th><Typography variant={"subheading"}>視聴者数</Typography></th>
          </tr>
        </thead>
        <tbody>
          {json.map((stream) => {
            return (
              <tr key={stream._id}>
                <td className="rank">
                  <Typography variant={"title"}>{i++}</Typography>
                </td>
                <td className="thumb">
                  <a href={stream.channel.url}>
                    <img src={stream.preview.medium} alt={stream.channel.status} />
                  </a>
                </td>
                <td className="detail">
                  <Typography variant={"title"} className="movie-title">{stream.channel.status}</Typography>
                  <Typography variant={"subheading"}>{stream.channel.display_name}</Typography>
                  <Typography variant={"subheading"} className="game-cat">
                    <GameIcon className="game-icon" />
                    <a href={"https://www.twitch.tv/directory/game/" + stream.channel.status}>
                      {stream.game}
                    </a>
                  </Typography>
                </td>
                <td className="viewers">
                  <Typography variant={"title"}>
                    <ViewerIcon className="viewer-icon" />
                    {stream.viewers}
                  </Typography>
                </td>
              </tr>
            )
          })}
        </tbody>
      </table>
    </Paper>
  )
};

Streams.propTypes = {
  rank: PropTypes.number,
  userName: PropTypes.string,
  movieTitle: PropTypes.string,
  gameTitle: PropTypes.string,
  viewers: PropTypes.number,
  url: PropTypes.string,
  thum: PropTypes.string,
};

export default TwitchRankPage
```

## スタイルを整える
現在のままでは見栄えが悪いです。そのため`プロジェクト名/src`配下に`twitch.css`を作成し、以下のようにしましょう。

```src/twitch.css
/* PC */
@media screen and (min-width: 767px) {
    root {
      padding: 0;
      margin: 0;
    }

    thead {
      padding-bottom: 100px;
    }

    .paper {
      text-align: center;
      margin: auto;
      margin-top: 30px;
      width: 1000px;
    }

    table {
      width: 1000px;
    }

    tr {
      border-bottom: 1px solid #c9c9c9;
      display: block;
      padding-top: 10px;
      padding-bottom: 10px;
    }

    .rank {
      width: 80px;
    }

    .thumb {
      padding: 0;
      width: 320px;
    }

    .detail {
      text-align: left;
      width: 380px;
      padding-left: 20px;
    }

    .movie-title {
      width: 380px;
      padding-right: 0;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }

    .game-cat {
      width: 380px;
      padding-left: 0;
      padding-right: 0;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }

    .viewers {
      padding-left: 20px;
      width: 150px;
    }

    .viewer-icon {
      padding-right: 10px;
      color: #ff0000;
      font-size: 30px;
      position: relative;
      top: 5px;
      left: 5px;
    }

    .game-icon {
      padding-right: 10px;
      color: #380B61;
      font-size: 25px;
      position: relative;
      top: 5px;
      left: 5px;
    }
}

/* SmartPhone */
@media screen and (max-width: 767px) {
  table {
    border-collapse: collapse;
    border-spacing: 0;
    width: 100vw;
    border: none;
  }

  thead {
    display: none;
  }

  tr {
    padding-top: 10px;
    padding-bottom: 10px;
    border-bottom: 1px solid #c9c9c9;
    display: block;
  }

  td {
    border-bottom: none;
    display: block;
    padding: 0;
  }

  .rank {
    display: none;
  }

  img {
      width: 100vw;
  }

  .detail {
    padding-right: 0;
  }

  .movie-title {
    width: 100vw;
    padding-right: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .game-cat {
    width: 380px;
    padding-left: 0;
    padding-right: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .viewers {
    padding-right: 30px;
    text-align: right;
  }

  .viewer-icon {
    padding-right: 10px;
    color: #ff0000;
    font-size: 30px;
    position: relative;
    top: 5px;
    left: 5px;
  }

  .game-icon {
    padding-right: 10px;
    color: #380B61;
    font-size: 25px;
    position: relative;
    top: 5px;
    left: 5px;
  }
}
```

## 完成
最後に`index.js`にTwitchRankPageコンポーネントを追加してください。コンソールから`npm start`コマンドで実行して動いたら完成です。動かない方はエラーをよく読んでみてください。

```src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import TwitchRankPage from './App';

ReactDOM.render(
  <TwitchRankPage />,
  document.getElementById('root')
);
```

# まとめ
みなさんできましたでしょうか。cssなどを変更してかっこよくカスタマイズしてみてください。今回初めてTwitchAPIを使ってみましたが、充実していていろいろできそうに感じました。みなさんもTwichAPIを使って何か作ってみてはいかがでしょうか。

<img src="https://qiita-image-store.s3.amazonaws.com/0/81341/80593705-ce52-1516-5283-c94c849a46cf.png" height=300> <img src="https://qiita-image-store.s3.amazonaws.com/0/81341/926faeff-1ca3-71f9-0300-d52ed8e651b9.png" height=300>