😁

urqlで無限ローディングUIを実装する

2022/07/07に公開

urqlで無限ローディングなUIを実装する方法がググってもそのまま真似できそうなパターンがあまり出てこなかったので記録しておこうと思います。

基本方針としてはドキュメントに書いてあるとおりに従います。
https://formidable.com/open-source/urql/docs/basics/ui-patterns/#infinite-scrolling

前提条件

以下の記事で紹介している無限スクロールのための汎用hookを使います。
https://zenn.dev/eringiv3/articles/70d20a2eeacf2e

実装

リストの最後の要素が画面内に収まったときに次ページをロードする実装です。

ArticleList.tsx
const LIMIT = 30

const ArticleList: React.VFC = () => {
  const [pageVariables, setPageVariables] = useState([
    {
      offset: 0,
      limit: LIMIT,
    },
  ])

  const handleReachEnd = useCallback(() => {
    setPageVariables((v) => {
      const lastPageVariable = v.at(-1)
      const offset = lastPageVariable
        ? lastPageVariable.offset + lastPageVariable.limit
        : null

      return offset ? [...v, { offset, limit: LIMIT }] : v
    })
  }, [])

  const { setLastElement } = useInfiniteScroll(handleReachEnd)

  return (
    <Box display="flex" flexDirection="column" gap="20px">
      {pageVariables.map((v, i) => (
        <ArticleListPage
          key={i}
          offset={v.offset}
          limit={v.limit}
          setRef={
            i === pageVariables.length - 1
              ? (ref) => {
                  setLastElement(ref)
                }
              : undefined
          }
        />
      ))}
    </Box>
  )
}

export { ArticleList }
ArticleListPage.tsx
type Props = {
  offset: number
  limit: number
  setRef?: (ref: HTMLDivElement | null) => void
}
const ArticleListPage: React.VFC<Props> = ({ offset, limit, setRef }) => {
  const [{ data: articlesResponse, fetching }] = useQuery<
    GetArticlesQuery,
    GetArticleQueryVariables
  >({
    query: getArticlesQuery,
    variables: {
      offset,
      limit,
    },
  })

  return fetching ? (
    <Box display="flex" justifyContent="center" paddingTop="20px">
      <Spinner />
    </Box>
  ) : (
    <SimpleGrid columns={2} gap="20px">
      {articlesResponse.length > 0 &&
        articlesResponse.map((article) => (
          <Box key={article.id} ref={setRef}>
            <ArticleCard article={article} />
          </Box>
        ))}
    </SimpleGrid>
  )
}

export { ArticleListPage }

Discussion