Rechartsを使って円グラフを表示させるポスト詳細画面の実装【初学者のReact×Railsアプリ開発 第11回】

#やったこと
* Reactでポスト詳細画面を実装した。
* rechartsを用いて円グラフを表示させた。
* ログイン中のユーザー情報によって、表示をコントロールした。
* 詳細画面の中で、投票の変更を行えるようにした。

#成果物
![jqe0k-3qqon.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/367226/c5a554ae-69ab-17eb-c9c9-ef175a490eb6.gif)

#Rails実装手順
###route.rb
```ruby:route.rb
Rails.application.routes.draw do
 namespace :api, defaults: { format: :json } do
    namespace :v1 do

      delete 'posts/:id', to: 'posts#destroy'

    end
 end
 root 'home#about'
end
```
###likes_controller, posts_controller
すでに実装済み。
[Ruby on Rails APIモードのCRUD実装 【初学者のReact✗Railsアプリ開発 第5回】](https://qiita.com/dl10yr/items/533cecd1d6f9abcfd13c)
[Ruby on Rails APIモードでいいね機能を実装する【初学者のReact×Railsアプリ開発 第6回】](https://qiita.com/dl10yr/items/848de567dac4a4e9cf0f)
#React実装手順
###App.js（ルーティング）
```javascript:App.js
import PostsDetail from './containers/PostDetail';
            <Auth>
              <Switch>
                <Route exact path="/" component={Home} />
                <Route path='/create' component={Create} />
                <Route path='/postslist' component={PostsList} />
                <Route exact path="/posts/:id" component={PostsDetail} />
              </Switch>
            </Auth>
```


###containers/PostDetail.js(render)
* 条件によって何を表示するかを分けています。
* renderGraphWithConditionでは、投票数が1票以上あるときと0票のときで表示内容を分けています。
* renderButtonWithConditionでは、ログイン中ユーザーのその投稿に対する投票情報で表示を分けています。
* renderDeleteButtonでは、自分が作成した投稿のとき削除ボタンを表示します。
* Scrollbars: react-custom-scrollbarsモジュールはめちゃ便利。

```javascript:PostDetail.js
  render() {
    const { CurrentUserReducer } = this.props;
    const isloggedin = CurrentUserReducer.isLoggedin;

    const { classes } = this.props;
    return (
      <Scrollbars>
        <div className={classes.textLeft}>

          {this.renderGraphWithCondition(this.state.all_count)}
          {this.renderButtonWithCondition(this.state.user_answer_suki)}
          {this.renderDeleteButton()}

        </div>
      </Scrollbars>
    );
  }
```

###containers/PostDetail.js(function)
* 難しいことはやっていないけど、コードを書き切るのはなかなか大変でした。
* ボタンを押したときに呼び出す関数に引数を渡したい。記述例: ```<Button onClick={() => this.ChangeLike(1)}>``` ([【React】イベントハンドラで引数を使いたい【備忘録】](https://qiita.com/tsuuuuu_san/items/73747c8b6e6e28f6bd23))


```javascript:PostDetail.js
  constructor(props) {
    super(props);
    this.state = {
      user_answer_suki: []
    };
    const auth_token = localStorage.auth_token
    const client_id = localStorage.client_id
    const uid = localStorage.uid
    const { CurrentUserReducer } = this.props;
    axios.get(process.env.REACT_APP_API_URL + `/api/v1/posts/${this.props.match.params.id}`, {
      headers: {
        'access-token': auth_token,
        'client': client_id,
        'uid': uid
      }
    })
      .then((response) => {
        const postdata = response.data.data;
        this.setState({
          suki_percent: postdata.post.suki_percent,
          kirai_percent: 100 - postdata.post.suki_percent,
          suki_count: postdata.post.suki_count,
          kirai_count: postdata.post.kirai_count,
          content: postdata.post.content,
          created_at: postdata.post.created_at,
          all_count: postdata.post.all_count,
          username: postdata.user.name
        });
      })
      .catch(() => {
        this.props.history.push('/')
      });

    axios.get(process.env.REACT_APP_API_URL + `/api/v1/likes/post/${this.props.match.params.id}/user/${uid}`, {
      headers: {
        'access-token': auth_token,
        'client': client_id,
        'uid': uid
      }
    })
      .then((response) => {
        const answereddata = response.data.data;
        this.setState({
          user_answer_suki: answereddata.suki,
          user_answer_updatedat: answereddata.updated_at,
        })
      })

    this.ChangeLike = this.ChangeLike.bind(this);
    this.DeletePost = this.DeletePost.bind(this);
    this.submitLike = this.submitLike.bind(this);

  }

  renderGraphWithCondition(all_count) {
    const { classes } = this.props;
    if (all_count != 0) {
      return (
        <Paper className={classes.root} elevation={1}>
          <Typography variant="headline" component="h1" className={classes.content}>
            {this.state.content}
          </Typography>
          <Typography component="p" style={{ fontWeight: 'bold' }}>
            created by {this.state.username}
          </Typography>
          <PieChart suki_percent={this.state.suki_percent} kirai_percent={this.state.kirai_percent} />
          <Typography component="p" style={{ fontWeight: 'bold', fontSize: '1.2rem' }}>
            スキ: {this.state.suki_percent}% ({this.state.suki_count}人)
            </Typography>
          <Typography component="p" style={{ fontWeight: 'bold', fontSize: '1.2rem' }}>
            キライ: {this.state.kirai_percent}% ({this.state.kirai_count}人)
            </Typography>
          <Typography component="p" style={{ fontWeight: 'bold' }}>
            投票数: {this.state.all_count}人
            </Typography>
        </Paper>
      )
    } else {
      return (
        <Paper className={classes.root} elevation={1}>
          <Typography variant="headline" component="h1" className={classes.content}>
            {this.state.content}
          </Typography>
          <Typography component="p" style={{ fontWeight: 'bold' }}>
            created by {this.state.username}
          </Typography>
          <Typography component="p" style={{ fontWeight: 'bold' }}>
            まだ誰も投票してません。
          </Typography>
          <Typography component="p" style={{ fontWeight: 'bold' }}>
            投票数: {this.state.all_count}人
            </Typography>
        </Paper>
      )
    }
  }

  renderButtonWithCondition(user_answer_suki) {
    const { classes } = this.props;
    if (user_answer_suki == 3) {
      return (
        <Paper className={classes.root}>
          <Button variant="contained" size="large" color="secondary" className={classes.button} onClick={() => this.submitLike(1)}>
            スキ
          </Button>
          <Button variant="contained" size="large" color="primary" className={classes.button} onClick={() => this.submitLike(0)}>
            キライ
          </Button>
        </Paper>
      )
    } else if (user_answer_suki == 2) {
      return (
        <Paper className={classes.root}>
          <Button variant="contained" size="large" color="secondary" className={classes.button} onClick={() => this.ChangeLike(1)}>
            スキ
          </Button>
          <Button variant="contained" size="large" color="primary" className={classes.button} onClick={() => this.ChangeLike(0)}>
            キライ
          </Button>
        </Paper >
      )
    } else if (user_answer_suki == 1) {
      return (
        <Paper className={classes.root}>
          スキで回答済み。
            <Button variant="contained" size="large" color="primary" className={classes.button} onClick={() => this.ChangeLike(0)}>
            キライに変更する
            </Button>
        </Paper>
      )
    } else if (user_answer_suki == 0) {
      return (
        <Paper className={classes.root}>
          キライで回答済み。
            <Button variant="contained" size="large" color="primary" className={classes.button} onClick={() => this.ChangeLike(1)}>
            スキに変更する
            </Button>
        </Paper>

      )
    }
  }

  renderDeleteButton() {
    const { CurrentUserReducer } = this.props;
    const { classes } = this.props;
    if (CurrentUserReducer.items.name === this.state.username) {
      return (
        <Button variant="contained" size="large" color="primary" className={classes.button} onClick={this.DeletePost}>
          このテーマを削除する
        </Button>
      )
    } else {
    }
  }

  ChangeLike(suki) {
    const auth_token = localStorage.auth_token
    const client_id = localStorage.client_id
    const uid = localStorage.uid
    axios.put(process.env.REACT_APP_API_URL + `/api/v1/likes/post/${this.props.match.params.id}`,
      {
        'suki': suki
      },
      {
        headers: {
          'access-token': auth_token,
          'client': client_id,
          'uid': uid
        }
      })
      .then((response) => {
        const postdata = response.data.data;
        this.setState({
          suki_percent: postdata.post.suki_percent,
          kirai_percent: 100 - postdata.post.suki_percent,
          suki_count: postdata.post.suki_count,
          kirai_count: postdata.post.kirai_count,
          content: postdata.post.content,
          created_at: postdata.post.created_at,
          all_count: postdata.post.all_count,
          username: postdata.user.name
        });
        const answereddata = response.data.data.like;
        this.setState({
          user_answer_suki: answereddata.suki,
          user_answer_updatedat: answereddata.updated_at,
        })
      })
  }

  DeletePost() {
    const { CurrentUserReducer } = this.props;
    const auth_token = localStorage.auth_token
    const client_id = localStorage.client_id
    const uid = localStorage.uid
    axios.delete(process.env.REACT_APP_API_URL + `/api/v1/posts/${this.props.match.params.id}`,
      {
        headers: {
          'access-token': auth_token,
          'client': client_id,
          'uid': uid
        }
      })
    window.history.back(-2)
  }

  submitLike(suki) {
    const { CurrentUserReducer } = this.props;
    const auth_token = localStorage.auth_token
    const client_id = localStorage.client_id
    const uid = localStorage.uid
    const data = {
      user_id: CurrentUserReducer.items.id,
      post_id: this.props.match.params.id,
      suki: suki,
    }
    axios.post(process.env.REACT_APP_API_URL + '/api/v1/likes', data, {
      headers: {
        'access-token': auth_token,
        'client': client_id,
        'uid': uid
      }
    })
      .then((response) => {
        const postdata = response.data.data;
        this.setState({
          suki_percent: postdata.post.suki_percent,
          kirai_percent: 100 - postdata.post.suki_percent,
          suki_count: postdata.post.suki_count,
          kirai_count: postdata.post.kirai_count,
          content: postdata.post.content,
          created_at: postdata.post.created_at,
          all_count: postdata.post.all_count,
          username: postdata.user.name
        });
        const answereddata = response.data.data.like;
        this.setState({
          user_answer_suki: answereddata.suki,
          user_answer_updatedat: answereddata.updated_at,
        })
      })
  }
```

###components/SimplePieChart.js
* 円グラフを表示させるモジュールとして、rechartsを用いました。
* rechartsのコードはこちらを参考にしました。http://recharts.org/en-US/examples/PieChartWithCustomizedLabel


```javascript:SimplePieChart.js
import React, { PureComponent } from 'react';
import {
  PieChart, Pie, Sector, Cell,
} from 'recharts';

const COLORS = ['#FF8042', '#0088FE',];

const RADIAN = Math.PI / 180;
const renderCustomizedLabel = ({
  cx, cy, midAngle, innerRadius, outerRadius, percent, index, name
}) => {
  const radius = innerRadius + (outerRadius - innerRadius) * 0.5;
  const x = cx + radius * Math.cos(-midAngle * RADIAN);
  const y = cy + radius * Math.sin(-midAngle * RADIAN);

  return (
    <text x={x} y={y} fill="white" textAnchor={x > cx ? 'start' : 'end'} dominantBaseline="central" style={{ fontWeight: 'bold', whiteSpace: 'pre-line' }}>
      {`${(percent * 100).toFixed(0)}%`}
    </text>
  );
};

export default class SimplePieChart extends PureComponent {
  //static jsfiddleUrl = 'https://jsfiddle.net/alidingling/c9pL8k61/';

  constructor(props) {
    super(props)
  }

  render() {
    const { suki_percent, kirai_percent } = this.props;
    const data = [
      { name: 'スキ', value: suki_percent },
      { name: 'キライ', value: kirai_percent },
    ];
    return (
      <PieChart width={300} height={300}>
        <Pie
          startAngle={90}
          endAngle={-270}
          data={data}
          cx={120}
          cy={120}
          labelLine={false}
          label={renderCustomizedLabel}
          outerRadius={100}
          fill="#8884d8"
          dataKey="value"
        >
          {
            data.map((entry, index) => <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />)
          }
        </Pie>
      </PieChart>
    );
  }
}
```