FirebaseとReactでページネーション
はじめに
受注のあったアプリをFirebase+React+Typescriptで開発中です。
ページネーションと書きましたが、実際は次へと前へのボタン二つで実装です。
(※そして少々回りくどいかもしれません!!申し訳ありません!!)
Firebaseのクエリでは本物ページネーションが難しいため、開発中のアプリはAlgoliaを導入しました。
Firebase+Algoliaの開発記事はまた書こうと思います。
流れ
今回使うメインのデータの型
interface USER { uid: string name: string createdAt: Date image: string }
今回用意するState
Stateの上が関連する説明として書いてます。
// fetchしたデータ、その時表示させる件数分しかsetしません const [ fetchData, setFetchData ] = useState<USER[]>([]) // ① // fetchDataで取得したデータの最初のtimestamp、ページを戻る場合に使う // ちなみにtimestamp(createdAt)は、Firebaseだと下記のデータが入る /* createdAt: { nanoseconds: 34000000 seconds: 1635892516 } */ const [ firstTime, setFirstTime ] = useState<Date>() // ② // fetchDataで取得したデータの最後のtimestamp,ページを進む場合に使う const [ lastTime, setLastTime ] = useState<Date>() // ③ // 全てのデータの最初のID const [ firstId, setFirstId ] = useState('') // ④ // 全てのデータの最後のID const [ lastId, setLastId ] = useState('') // ⑤
1.対象のデータをfetchして①のfirstTimeへsetする。
2.fetchしたデータの最初と最後のtimestamp(createdAt)をstateへset、最初の値を②のfirstTime、最後の値を③lastTimeへsetする。
3.fetchしたデータの最初と最後のID(uid)をstateへset、最初の値を④のfirstId、最後の値を⑤lastIdへsetする。
4.fetchしたデータに2でsetした最初と最後IDがあれば、ページを戻ったり進められないよう処理する。
※今回はフロント部分で矢印のボタンを制御するようにしました。
firstIdもlastIdも合致していない場合、どちらの矢印も表示させる
fetchDataの最初のデータのIDがfirstIdと合致している場合、戻る矢印は表示させない
fetchDataの最後のデータのIDがlastIdと合致している場合、進む矢印は表示させない
tsxで書くとこんな感じ
{ fetchData[0].uid != firstId && 戻る矢印 } { fetchData[fetchData.length - 1].uid != lastId && 進む矢印 }
5.次に進む場合は、2でsetした③lastTimeの次のリストからデータをfetchする。
この時FirebaseのメソッドでstartAfterを使う。
6.前に戻る場合は、2でsetした②firstTimeの手前のリストからデータをfetchする。
この時fetchするデータをlimitで制限している場合、進むとは違って、limitToLastというメソッドとendBeforeを掛け合わせて使う。
という感じです。
下準備が多く、わかりにくく申し訳ありません…
これいらないんじゃないの?とか別の方法があれば教えてもらえたら幸いです!
コード
ざっくりとまとめるとこんな感じです。
今回fetchするデータはcollectionのusersを9件ずつ取得することにしてます。
フロント部分は最小限ですし、CSSも当たってないのでデザインしてください。
Pagination.tsx import React, { useEffect, useState } from 'react' import { db } from '../../../firebase' // ディレクトリ適当です、アプリごと変わります interface USER { uid: string name: string createdAt: Date image: string } const Pagination = () => { const usersRef = db.collection('users') const [fetchData, setFetchData] = useState<USER[]>([]) const [firstTime, setFirstTime] = useState<Date>() const [lastTime, setLastTime] = useState<Date>() const [firstId, setFirstId] = useState('') const [lastId, setLastId] = useState('') useEffect(() => { fetchUsersData() }, []) // setStateをまとめてます const setMethod = (dataList: USER[]) => { setFirstTime(dataList[dataList.length - 1].createdAt) setLastTime(dataList[0].createdAt) setFirstId(dataList[dataList.length - 1].uid) setLastId(dataList[0].uid) } // 一番最初の9件を取りに行く const fetchUsersData = () => { let dataList: USER[] = [] usersRef .limit(9) .orderBy('createdAt', 'desc') .get() .then((snapshot) => { snapshot.forEach((doc) => { const data = doc.data() const newData = { uid: data.uid, name: data.name, createdAt: data.createdAt, image: data.image, } dataList.push(newData) }) setFetchData(dataList) setMethod(dataList) }) } // 次のページ分を取りに行く // startAfterが指定したcreatedAt以降のデータを取ってきてくれる const fetchNextUsersData = () => { let dataList: USER[] = [] usersRef .limit(9) .orderBy('createdAt', 'desc') .startAfter(lastTime) .get() .then((snapshot) => { snapshot.forEach((doc) => { const data = doc.data() const newData = { uid: data.uid, name: data.name, createdAt: data.createdAt, image: data.image, } dataList.push(newData) }) setFetchData(dataList) setMethod(dataList) }) } // 前のページ分を取りに行く // endBeforeとlimitToLastで指定したcreatedAtより手前のデータを取ってきてくれる const fetchPrevUsersData = () => { let dataList: USER[] = [] usersRef .limitToLast(9) .orderBy('createdAt', 'desc') .endBefore(firstTime) .get() .then((snapshot) => { snapshot.forEach((doc) => { const data = doc.data() const newData = { uid: data.uid, name: data.name, createdAt: data.createdAt, image: data.image, } dataList.push(newData) }) setFetchData(dataList) setMethod(dataList) }) } return ( <> {fetchData.map((fd: USER) => ( <div> <img src={fd.image} /> </div> ))} <div className='prevBtn'> {fetchData[0].uid != firstId && <img src={左矢印} onClick={() => fetchPrevUsersData()} />} </div> <div className='nextBtn'> {fetchData[fetchData.length - 1].uid != lastId && <img src={右矢印} onClick={() => fetchNextUsersData()} />} </div> </> ) } export default Pagination
これでうまく機能すると思います!