React-nativeでInfinite Scrollのページネーションを実装する

# 目的・環境
- Firebase→React-native→FlatListの構成でページネーションを実装する
- 読み込み方法はInfinite Scrollを行う
- 既存の日本語の記事では、Reduxを交えた高度な構成であったり、React-nativeのFlatListを利用した事例がなかったり、何より、最小限のシンプルな例が見当たらなかったため、整理することにした

# ゴール
- Firebaseでページネーションを行う
- React-natigve、アプリでスクロールで更新する
- `FlatList` を使う ＜ーこれを重視しました。よく使うリストの作り方だと思うので
- 最小限の部分を把握する

# 実装
## 概要
- Firebaseに、createtAt でタイムスタンプを設定している。そのタイムスタンプ降順でソートする    
今回はタイムスタンプだが、idでもなんでもソートできればいい
- 一度の読み込みサイズは２レコード
- ページ下部到達で、追加の２レコードを表示する
- 読み込み中はインジケータを表示する

## コードと解説
- ポイント
 - stateに最後に読んだデータを記録しておく ->lastVisible
 - lastVisibleは、orderbyでソート軸にした項目
 - クエリの条件で `.startAfter(lastVisible)` を追加しカーソルを設定  
 言うまでもなく `.limit(バッチサイズ)` で絞る
 - FlatListに属性を追加する `onEndReached={this.fetchMore} onEndReachedThreshold={0}`
 - リストが最下部にきたら `onEndReached`　で指定したメソッドが実行される。そこで再読み込み処理を書く

```javascript:app.js

class サンプルクラス extends React.Component {
  state = {
    items: [],
    refreshing: false,
    limit: 2, // 今回はここに読み込むバッチサイズを定義しておく。
    lastVisible: null,
    loading: false,
  }

constructor(props) {
    super(props);
    this.listLoading(props); // 初回ロードを実行

  }
  // 初回ロード　リストを遅延ローディングする
　// ここは単純に画面表示直後のデータを取り込んでいるだけ
  // チェックするポイントは以下
  listLoading = async (props) => {
    // 読み込み中を開始する
    this.setState({
        loading: true,
      });
　　　const firestore = firebase.firestore();
　　　await firestore.collection"コレクション")
     // 今回は、ソート順を、作成時刻降順、としているため、タイムスタンプを利用
     .orderBy("createdAt", "desc") 
     .limit(limit).then((items) => {
        // 取得結果の最後をstateに保持する
        let lastVisible = items[items.length - 1].createdAt;
        this.setState({
          items : items,  //リストに表示するデータ
          loading: false, //　読み込み中を完了する
          lastVisible:lastVisible, //　現在読み込んでいる最後のデータを保持する（次に読み込むとき、「これより後」と絞る）
     })
   })



  // 再読み込み　リストが最下部に到着したら読み込み
  fetchMore =async  () => {
　　　　　　if(this.state.refreshing){　// すでにrefreshingが実行されていたら抑止する　注釈1)
      console.log("二重読み込み抑止")
      return
    }
    console.log('追加読み込み開始');
    this.setState({
        refreshing: true,
    });

　　　　　　　const firestore = firebase.firestore();
    let additionalQuery = await firestore.collection("コレクション")
     .orderBy("createdAt", "desc")
     // 前回読み込んだ最後をカーソルに設定し、そこから、limitまで取得する
    // orderByで定義した方向にlimit分取得する
    // lastVisibleは、orderByで定義した項目と同じものを設定する
     .startAfter(this.state.lastVisible)
     .limit(limit)
    
     let documentSnapshots = await additionalQuery.get()
     let items = []
     documentSnapshots.forEach(function(doc) {
       var res = doc.data();    
       items.push(res);
     })
     // 追加取得したデータの最後のデータをstateに記録する
     // orderBy("createdAt", "desc") としているのでcreatedAtを保持する
     let lastVisible = items[items.length - 1].createdAt;
     // 取得したデータを、既存のリストのデータの後ろにつける
     let itemsList = this.state.items
     for(let item of items) {
       itemsList.push(item)
     }
     // stateを更新し、loding状態を終了する
     this.setState({
       items : itemsList,
       loading: false,
       lastVisible:lastVisible,
       refleshing:false // リフレッシュ状態を終了
     })
  }

// ローディングのインジケータ　下部ページング
  renderFooter = () => {
    if (this.state.refreshing) {
      return <ActivityIndicator size="large" />;
    } else {
      return null;
    }
  };

  render() {

    return(
      <View>
        <FlatList
        onEndReached={this.fetchMore} // 表示末端まで到達時に、呼び出し
        onEndReachedThreshold={0.1}     // 感度を指定。再読み込みを行う閾値を設定する
        ListFooterComponent={this.renderFooter} //画面下部のインジケータを表示
        refreshing={this.state.refreshing} 
        data={this.state.items}
        extraData={this.state.refreshFlatList}
        renderItem={this.getItem}
        keyExtractor={item => item.id}

         />
      </View>
    )
  }
}
```

- 注釈1)
 - リフレッシュした結果、リストに重複した行が挿入されてしまう問題
 - 原因は、インフィネートスクロールの読み込みイベントがダブって実行されてしまっていること。処理が完了してlastVisibleが更新される前に再実行されてしまい、結果が重複する
 - 対策は、すでにローディングに入っていたら、それが完了するまで、ローディング処理に入れないようにする

# 参考資料
> - もっともわかりやすかった資料
>Implementing Infinite Scroll (Lazy Loading) With FlatList Using React Native + Firebase Cloud Firestore
> https://levelup.gitconnected.com/react-native-firebase-cloud-firestore-implementing-infinite-scroll-lazy-loading-with-flatlist-a9e942cf66c6

> - 公式の関連するドキュメント
> クエリカーソルを使用したデータのページ設定
> https://firebase.google.com/docs/firestore/query-data/query-cursors?hl=ja

> - その他
> Cloud Firestore でのデータの並べ替えと制限
> https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja
> コピペで使うFirebase【Firestore ページング編】
> https://qiita.com/ovama-koffee/items/c025e3d7d888c4a9a6e3