Semantic-ui-reactを用いて一意のURLに対応したページネーションを実装した【React】

# 概要

`semantic-ui-react`を用いてページネーションの機能を実装していたのですが、
ページネーションの番号とURLが一意に対応するように実装をしてみました。

これによって、例えば戻るボタンで別ページからページネーションのページに戻った際に
常にデフォルトで設定されてるページ番号に遷移してしまう現象を防げます。

# 実装
まずはページネーションで扱う変数を定義します。

```react:List.tsx
interface ArticleState {
  articleFromApi: Article[];
  articleToDisplay: Article[];
  begin: number;
  end: number;
  activePage: number;
  redirect: boolean;
}
```

- `articleFromApi` -> apiから取得した全データ
- `articleToDisplay` -> `articleFromApi`からページネーションで表示するデータだけ抽出したもの
- `begin` -> `articleFromApi`から`articleToDisplay`に抽出する初めのデータの数字を記録
- `end` -> `articleFromApi`から`articleToDisplay`に抽出する最後のデータの数字を記録
- `activePage` -> クリックされたorされているページネーション番号を記憶しておく変数

コンストラクタを設定します

```react:List.tsx
  constructor(props: RouteComponentProps<{id: string}>) {
    super(props);
    this.state = {
      articleFromApi: [],
      articleToDisplay: [],
      begin: 0,
      end: 5,
      activePage: 1,
      redirect: false,
    };
    this.getArticle = this.getArticle.bind(this);
    this.pageChange = this.pageChange.bind(this);
    this.setList = this.setList.bind(this);
    this.setActiveList = this.setActiveList.bind(this);
  }
```
- デフォルトではページネーション先頭の「1」番目を開くように`activePage`: `1`と設定しています。
- １ページにつき5つのデータが表示されるようにしているので、初期の`start`と`end`はそれぞれ`0`,`5`です。

```react:List.tsx
  async pageChange(
    event: React.MouseEvent<HTMLAnchorElement>,
    data: PaginationProps
  ) {
    await this.setState({activePage: data.activePage as number});
    await this.setActiveList();
    this.setState({redirect: true});
  }

  async setActiveList() {
    await this.setState({begin: this.state.activePage * 5 - 5});
    await this.setState({end: this.state.activePage * 5});
    this.setState(
      {
        articleToDisplay: this.state.articleFromApi.slice(
          this.state.begin,
          this.state.end
        ),
      },
      () => window.scrollTo(0, 0)
    );
  }

  async componentDidMount() {
    this.setState({articleFromApi: []});
    await this.getArticle();
    await this.setState({activePage: Number(this.props.match.params.id)});
    this.setActiveList();
  }
```
- 直接URLを叩いた際は`componentDidMount()` <- `setActiveList()`の順で参照されて、`articleToDisplay`が形成されます。
- ページネーションコンポーネントがクリックされた際は`pageChange()` <- `setActiveList()`の順で参照されて、`articleToDisplay`が形成されます。
- いずれの場合もやっていることは、ページネーション番号から表示するデータを抽出する作業です。

最後にコンポーネントをrenderします。

```react:List.tsx
  render() {
    return (
      <Container style={{marginTop: '3em'}} text>
        <Grid columns={1} divided="vertically">
          <Grid.Row>
            {(this.state.articleToDisplay || []).map(function(articleData, i) {
              return (
                <Grid.Column>
                  <Segment>
                    <Header as="h1">{articleData.title}</Header>
                    <p style={{textOverflow: 'clip', wordBreak: 'break-all'}}>
                      {articleData.content.length > 100
                        ? articleData.content.substring(0, 97) + '...'
                        : articleData.content}
                    </p>
                    <Link to={`/detail/${articleData.id}`}>
                      continue reading
                    </Link>
                  </Segment>
                </Grid.Column>
              );
            })}
          </Grid.Row>
        </Grid>
        <Pagination
          defaultActivePage={this.props.match.params.id}
          totalPages={Math.ceil(this.state.articleFromApi.length / 5)}
          onPageChange={this.pageChange}
        />
        {this.renderRedirect()}
      </Container>
    );
  }
```
- `Grid`内で`articleToDisplay`を描写しています。
- `Pagination`内でページネーションの諸々を設定しています。`defaultActivePage`はURLのパラーメーターと一致するように設定しています。`totalPages`は１ページあたり5つのデータに対応するように設定しています。

また、URLのパラメーターとページネーション番号が一致するようにApp.tsxを設定します。

```react:App.tsx
<Route exact path="/:id" component={List} />
```
```react:List.tsx
export default withRouter(List);
```

# ソースコード（全体）
以下のレポジトリに全貌のソースコードがあります。
https://github.com/jpskgc/article/blob/master/client/src/components/List.tsx