HOW-TO GUIDES

리액트 파이어베이스에서 무한 스크롤하는 방법

encredible 2021. 9. 22. 20:15

React를 프론트엔드로, Firebase를 백엔드로 사용(정확히는 firestore)하는 경우 무한 스크롤(Infinite Scroll)을 하는 방식에 대해 설명하겠습니다.

화면 최하단으로 스크롤을 내리는 이벤트 리스너

스크롤이 화면 최하단에 닿는 것은 react-bottom-scroll-listener library를 통해 간편하게 할 수 있습니다. (자세한 사용법은 링크를 타고 들어가 확인하시길 바랍니다)

useBottomScrollListener(callback)

사용법이 간단합니다. npm install react-bottom-scroll-listner 를 하신 후에 위와 같이 callback 을 전달해주면 됩니다. callback으로 page를 채울 대상을 받아오면 됩니다. console.log 등을 통해 잘 동작하는지 확인해보면 좋습니다.

나머지 코드 설명

const db = getFirestore()
let lastVisible: any = undefined
const Home: NextPage = () => {
  const [posts, setPosts] = useState<Post[]>([])
  const getNextPosts = () => {
    let q
    if (lastVisible === -1) {
      return
    } else if (lastVisible) {
      q = query(collection(db, 'posts'), orderBy('timestamp', 'desc'), limit(2), startAfter(lastVisible))
    } else {
      q = query(collection(db, 'posts'), orderBy('timestamp', 'desc'), limit(4))
    }

    getDocs(q).then((snapshot) => {
      setPosts((posts) => {
        const arr = [...posts]
        snapshot.forEach((doc) => {
          arr.push(doc.data() as Post)
        })
        return arr
      })
      if (snapshot.docs.length === 0) {
        lastVisible = -1
      } else {
        lastVisible = snapshot.docs[snapshot.docs.length - 1]
      }
    })
  }

  useBottomScrollListener(getNextPosts)

  useEffect(() => {
    getNextPosts()
  }, [])

  return (
    <div className={styles.container}>
      <div className={styles.main}>
        {posts.map((post) => (
          <PostItem key={post.link} post={post} />
        ))}
      </div>
    </div>
  )
}

먼저 query(collection(db, 'posts'), orderBy('timestamp', 'desc'), limit(4)) 를 이해하셔야 합니다. db 라는 firestore에서 'posts'라는 collection(일종의 폴더)에 질의를 날릴 것이고, 'timestamp'라는 'column'에 대해 'desc', 즉 내림차순으로 4개만 가져오겠다는 것입니다. lastVisible이 undefined인 경우에만 여기로 들어오게 하여 최초에 페이지를 불러오는 경우에는 4개의 아이템을 가져오도록 하였습니다. Firebase 공식 한글 문서의 웹 버전 9를 참고하시면 유사합니다.

다음으로 lastVisible이 true인 경우 startAfter라는게 추가 되는데 특정 document를 여기에 집어 넣으면 해당 document 뒤에서부터 원소를 가져오도록 되어 있습니다. 마지막 원소의 다음부터 가져오니 자연스럽게 스크롤이 이어지게 됩니다. 마지막 원소를 저장하는 것은 lastVisible = snapshot.docs[snapshot.docs.length - 1] 부분입니다. 스냅샷 도큐먼트들의 마지막 원소를 lastVisible에 저장해줍니다.

query를 하다보면 모든 원소를 다 가져오게 될 수 있는데 그 경우를 따로 처리하지 않으면 처음부터 다시 읽도록 되어 있습니다만, 저는 마지막에 도달하면 스크롤을 멈추도록 했습니다.(물론 제가 한 처리를 제외하는 경우 자연스럽게 처음 아이템부터 다시 나옵니다) query의 결과가 더 이상 없는 경우 lastVisible=-1을 할당하고 lastVisible=-1인 경우에는 더이상 query를 날리지 않도록 하였습니다.