react-infinite-scroll-component 備忘録

今回はreact-infinite-scroll-componentを触る機会があったので、備忘録がてらご紹介しようと思います。（ちなみに技術はReact + Reduxです！）
##### [react-infinite-scroll-component](https://github.com/ankeetmaini/react-infinite-scroll-component)

npm trendsで調べた結果こちらです。
[react-bidirectional-infinite-scroll vs react-infinite-scroll-component vs react-infinite-scroller vs react-lazyload vs react-infinite vs react-list](https://www.npmtrends.com/react-bidirectional-infinite-scroll-vs-react-infinite-scroll-component-vs-react-infinite-scroller-vs-react-lazyload-vs-react-infinite-vs-react-list)

<img width="1118" alt="1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/51686bde-4e63-8141-4e3e-e23ca072da9d.png">

<img width="1100" alt="2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327745/731bd158-b4d6-542d-42e7-fd34b8bf2e79.png">

### 使い方
参考となるコードはこちらです。
[infinite scroll in an element (div of height 400px)](https://codesandbox.io/s/w3w89k7x8)

下は主なreact-infinite-scroll-componentのpropsの指定についてです。

|  name  |  type  |  description  |
| ---- | :--- | :--- |
|  next  |  function  |  表示している配列の最後までスクロールが到達し終わった時の、次のfetchする処理  |
|  hasMore  |  boolean  |  スクロールし終わって、まだ配列を追加する場合はtrue、スクロール完了の時はfalse  |
|  dataLength  |  number  |  表示するデータのlength  |
|  loader  |  node  |  スクロール後の取得処理時間中、ローディングを表示できる  |
|  endMessage  |  node  |  スクロールし終わって読み込みが全て完了した時に表示するメッセージテキスト  |
|  height  |  number  |  指定すると指定した高さの中でスクロールする  |
|  pullDownToRefresh  |  bool  |  プルダウンからリフレッシュ機能を有効にする  |


### Reduxと合わせて実装する

以下が大体の処理内容です。

* fetchする時queryに、次に読み込む記事情報（ここでは`cursor`としました）を渡すようにする
* reducerのステートでは取得した記事一覧とcursorを管理しておく
* fetchして取得したX番目〜Y番目までの配列をreducersで元の配列に対して追加処理

### 最終的なコード

#### コンポーネント側
初期fetchを`componentDidMount()`内で実行しています。
`<InfiniteScroll />`では
①`dataLength`をreducersで管理している都度取得する配列の長さを指定
②`next`はactionsで実装した`fetchFeeds`を直接呼べ出し。パラメータには次に呼び出す`cursor`を渡してあげる
③`hasMore`には、最後まで到達したらバックエンドから`cursor: null`が返ってくることを想定して、`null`以外を`true`、最後に達したら`false`に指定

```javascript
import React from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { connect } from 'react-redux'

class Content extends React.Component {
  componentDidMount() {
    this.props.fetchFeeds({
      cursor: null, // 初期fetch
    })
  }

  render() {
    return (
      <InfiniteScroll
        dataLength={this.props.postFeeds.length}
        next={() => {
          this.props.fetchFeeds({
            cursor: this.props.cursor
          })
        }}
        hasMore={this.props.cursor != null}
      >
        <div>
          {this.props.postFeeds.map((item) => (
            <Card key={item.id}>
              {item.text}
            </Card>
          ))}
        </div>
      </InfiniteScroll>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    postFeeds: state.posts.feeds,
    cursor: state.posts.cursor,
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    fetchFeeds: (params) =>
      dispatch(
        asyncActions.fetchFeeds(params)
      ),
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Content)
```


#### actions
Actionではfetchする関数だけ実装しました。
シンプルですがこんな感じです！

```javascript
const fetchFeedsStart = () => ({
  type: ActionTypes.FETCH_FEEDS_START,
})
const fetchFeedsDone = (payload) => ({
  type: ActionTypes.FETCH_FEEDS_DONE,
  payload,
})
const fetchFeedsFailed = (payload) => ({
  type: ActionTypes.FETCH_FEEDS_FAILED,
  payload,
})

const fetchFeeds = (params) => {
  return async (dispatch) => {
    dispatch(fetchFeedsStart())
    try {
      const result = await sampleAPIClient.fetchFeeds(params)
      dispatch(fetchFeedsDone({ result }))
    } catch (error) {
      dispatch(fetchFeedsFailed({ error }))
    }
  }
}
```

#### reducers
reducersでは初期値`initialState`、fetchして受け取ったレスポンスを`concat`関数で配列に追加するようにしています。
シンプルですがこんな感じです！

```javascript
import { ActionTypes } from './actions'
import produce from 'immer'

const initialState = {
  …
  posts: {
    postFeeds: [],
    cursor: null,
  },
  …
}

export const reducer = (state = initialState, action) => {
  switch (action.type) {
    …
    case ActionTypes.FETCH_FEEDS_DONE: {
      return produce(state, (draft) => {
        draft.posts.feeds = draft.posts.feeds.concat(
          action.payload.result.items
        )
        draft.posts.cursor = action.payload.result.cursor
      })
    }
    …
    default:
      return state
  }
}
```

## 触ってみた所感
実装方法はこちらのYouTubeでも同じライブラリが使われていました。
[React Infinite Scroll Challenge | Scotch.io](https://www.youtube.com/watch?v=gk_6BKiy6X4)
npm trendsを見ると確実にreact-infinite-scroll-componentではないんですけどねぇ(^_^;)
react-infinite-scrollerの方が人気があるようですので、使うならこちらかも（？）
じゃあなぜ前者を紹介したかと言うと、この２つのライブラリに対して差がなかったからです！
また、YouTubeやブログ記事を見ると、ライブラリを使わず`addEventListener`で直に実装する方法もあるようで👀
有名どころのライブラリなら使う派ですが、無限スクロールを実装する手段が何種類かあることに驚きました！「これ」と言う正解が私は見つかりませんでした。