web

react-infinite-scrollerを使ってFirestoreで無限スクロールを実装する

公開日:2022年1月25日
最終更新日:2022年1月25日
目次
  • インストール
  • 基本的な使い方
  • Firestoreで追加読み込み
  • やること
  • 実際のコード
  • 参考サイト

Firestoreでデータを追加読み込みする時に、無限スクロールにするためにreact-infinite-scrollerというライブラリを使ってみました。
https://github.com/danbovey/react-infinite-scroller

react-infinite-scroll-componentというライブラリもあるようですが、react-infinite-scrollerの方がGithubスターの数が多いので、今回はreact-infinite-scrollerを選びました。
https://github.com/ankeetmaini/react-infinite-scroll-component

インストール

パッケージを追加します

yarn add react-infinite-scroller


基本的な使い方

リストをInfiniteScrollコンポーネントで囲うだけです。スクロールしてリストの最後に差し掛かる時にloadmoreの関数が発火します。

import InfiniteScroll from "react-infinite-scroller"

// 省略

<InfiniteScroll
  loadMore={loadMore}
  hasMore={true} 
  loader={<div key={0}>loading</div>}
>
    <ul>
      {list.map((value) => <li>{value}</li>)}
     </ul>
</InfiniteScroll>


Firestoreで追加読み込み


Firestoreではoffsetがないので、追加読み込み時はtimestampを使って読み込みした最後のリスト移行から追加読み込みするようにします。

やること


例えば15件ずつ取得する時にやることをざっと書き出すと...

  1. まずは普通に15件取得
  2. 取得した15件のリストの最後のtimestampをstateに保存
  3. 全てのリストのうち最後の1件のidを取得しておく(追加読み込みするかどうかを判定するため)
  4. react-infinite-scrollerでloadmoreが発火
  5. 2で保存したtimestampの次のリストから15件を取得する(startAfterを使う)
  6. 現在のリストと5で取得したリストを結合
  7. 上記を繰り返す
  8. リストの最後のidと3で取得したidが合致する場合は、これ以上ないと判断し追加読み込みはしない


実際のコード

いろいろ省略して雑ですがこんな感じです

const [lastDate, setLastDate] = useState(null)
const [todoList, setTodoList] = useState<TodoListItem[]>([])

useEffect(() => {
  getLast()
  getTodos()
}, [])

// 最後のidを取得
const getLast = async () => {
  const res = await getDocs(
    query(
      collection(db, 'users', uid, 'todos'),
      orderBy('createdAt', 'asc')
      .limit(1)
    )
  )

  setOldestId(res.docs[0].id)
}

// リスト取得
const getTodos = async () => {
  let fetchTodos = await getDocs(
    query(
      collection(db, 'users', uid, 'todos'),
      orderBy('createdAt', 'desc')
    )
  )

  // lastDateがある場合は初回読み込みではないと判断しstartAfterで追加読み込みする
  if (lastDate) {
    // リストの最後のidと全てのリストの最後のidが同じ場合は追加読み込みしない
    if (oldestId === todoList[todoList.length - 1].id) {
      return
    }
    fetchTodos = fetchTodos.startAfter(lastDate)
  }

  const res = await fetchTodos.limit(15).get()

 const todos: TodoListItem[] = res.docs.reduce(
   (acc: any, doc: any) => [
     ...acc,
     {
       id: doc.id,
       text: doc.data().text,
     },
   ],
   todoList
  )
  // リストのstateに生成したリストを追加
  setTodoList(todos)
  
  // 新しく取得したリストのうち最後のtimestampを保存
  setLastDate(res.docs[res.docs.length - 1].data().createdAt)
  setIsLoading(false)
}

<InfiniteScroll
  loadMore={getTodos}
  hasMore={oldestId !== todoList[todoList.length - 1].id}
>
  <Timeline className={styles.styledTimeline}>
    {todoList.map((item) => {
      return (
        <Timeline.Item key={item.id}>
          <div>
            {item.text}
          </div>
        </Timeline.Item>
      )
    })}
  </Timeline>
</InfiniteScroll>


参考サイト

About the author

大阪でフロントエンドエンジニアをしています。写真を撮るのが趣味です。よかったら500pxに載せてる写真も見てください。
web上に公開しているので、正確さに可能な限り努力してますが、個人の備忘録程度に書いてるので、ご自身の判断で参考程度に読んでください。
間違いやご意見があれば、コンタクトやSNSに気軽にご連絡ください。

Read next

Category

  • web
  • 雑記