-
-
Save dhruva81/25b1ae3c94ed643b317094d72678cfdb to your computer and use it in GitHub Desktop.
Infinite Scroll And Filters With React Query
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Container from "components/ui/Container" | |
import VideoCard from "components/VideoCard" | |
import fetchData from "helpers/fetchData" | |
import { useEffect, useState, Fragment, useRef } from "react" | |
import { useInfiniteQuery } from "react-query" | |
import useIntersectionObserver from "../hooks/useIntersectionObserver" | |
import Select from "react-select" | |
import { useUIDSeed } from "react-uid" | |
import { useRouter } from "next/router" | |
import Seo from "components/Seo" | |
import type { GetServerSideProps } from "next" | |
import type { Video } from "types" | |
interface Tag { | |
id: number | string | |
name: string | |
slug: string | |
} | |
interface AllVideosProps { | |
videos: { | |
pages: [ | |
{ | |
posts: [Video] | |
}, | |
] | |
pageParams: [number | undefined] | |
} | |
tags: [Tag] | |
} | |
interface QueryKeyType { | |
pageParam: number | |
queryKey: [[string]] | |
} | |
const getMoreVideos = async ({ pageParam = 1, queryKey }: QueryKeyType) => { | |
const [tags] = queryKey | |
const tagsQueryString = tags.join(",") | |
if (tagsQueryString !== "") { | |
const videos = await fetchData(`/tags/${tagsQueryString}?page=${pageParam}`) | |
return videos | |
} | |
const videos = await fetchData(`/all-videos?page=${pageParam}`) | |
return videos | |
} | |
function AllVideos({ videos, tags }: AllVideosProps) { | |
const seed = useUIDSeed() | |
const router = useRouter() | |
const [tagIds, setTagIds] = useState([]) | |
const { data, isSuccess, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteQuery( | |
[tagIds], | |
getMoreVideos, | |
{ getNextPageParam: (page) => (page.current_page === page.last_page ? undefined : page.current_page + 1) }, | |
{ initialData: videos }, | |
) | |
const loadMoreRef = useRef() | |
useIntersectionObserver({ | |
target: loadMoreRef, | |
onIntersect: fetchNextPage, | |
enabled: hasNextPage, | |
}) | |
return ( | |
<Container> | |
<Seo | |
title="Browse all tutorials" | |
currentUrl={router.asPath} | |
description="Browse all tutorials" | |
imageUrl="/images/default-image.jpg" | |
/> | |
<h2 className="my-8 lg:my-20 text-2xl md:text-4xl lg:text-6xl font-bold">Browse All Tutorials</h2> | |
<div className="mb-8 bg-gray-50 p-4 inline-block w-full md:w-1/3"> | |
<div className="w-full"> | |
<Select | |
getOptionLabel={(option) => option.name} | |
getOptionValue={(option) => option.id} | |
options={tags} | |
isMulti | |
placeholder="Filter by tag" | |
instanceId="tags" | |
onChange={(values) => setTagIds(values.map((tag) => tag.id))} | |
/> | |
</div> | |
</div> | |
<div className="md:flex md:flex-wrap md:justify-between"> | |
{isSuccess && | |
data?.pages.map((page) => ( | |
<Fragment key={seed(page)}> | |
{page.data.map((video: Video) => ( | |
<VideoCard key={video.id} video={video} /> | |
))} | |
</Fragment> | |
))} | |
</div> | |
<div ref={loadMoreRef} className={`${!hasNextPage ? "hidden" : ""}`}> | |
{isFetchingNextPage ? "Loading more..." : ""} | |
</div> | |
{isLoading && ( | |
<div className="text-center bg-gray-50 p-8 rounded-md text-gray-400 text-xl mt-14"> | |
Loading videos! ❤️ | |
</div> | |
)} | |
{!hasNextPage && !isLoading && ( | |
<div className="text-center bg-gray-50 p-8 rounded-md text-gray-400 text-xl mt-14"> | |
Congrats! You have scrolled through all the tutorials. You rock! 🤘 | |
</div> | |
)} | |
</Container> | |
) | |
} | |
export const getServerSideProps: GetServerSideProps = async () => { | |
const data = await fetchData("/all-videos") | |
const tags = await fetchData("/tags") | |
const videos = { | |
pages: [{ data }], | |
pageParams: [null], | |
} | |
return { | |
props: { | |
videos, | |
tags, | |
}, | |
} | |
} | |
export default AllVideos |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "styles/globals.css" | |
import type { AppProps } from "next/app" | |
import { ThemeProvider } from "@emotion/react" | |
import Header from "components/Header" | |
import Footer from "components/Footer" | |
import theme from "theme/theme" | |
import { QueryClientProvider, QueryClient } from "react-query" | |
const queryClient = new QueryClient() | |
function MyApp({ Component, pageProps }: AppProps) { | |
return ( | |
<ThemeProvider theme={theme}> | |
<Header /> | |
<QueryClientProvider client={queryClient}> | |
<Component {...pageProps} /> | |
</QueryClientProvider> | |
<Footer /> | |
</ThemeProvider> | |
) | |
} | |
export default MyApp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function fetchData(url: string) { | |
const apiurl = process.env.API_URL ? process.env.API_URL : process.env.NEXT_PUBLIC_API_URL | |
const data = fetch(`${apiurl}${url}`).then((res) => res.json()) | |
return data | |
} | |
export default fetchData |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useEffect } from "react" | |
export default function useIntersectionObserver({ | |
enabled = true, | |
onIntersect, | |
root, | |
rootMargin = "0px", | |
target, | |
threshold = 0.1, | |
}) { | |
useEffect(() => { | |
if (!enabled) { | |
return | |
} | |
const observer = new IntersectionObserver( | |
(entries) => entries.forEach((entry) => entry.isIntersecting && onIntersect()), | |
{ | |
root: root && root.current, | |
rootMargin, | |
threshold, | |
}, | |
) | |
const el = target && target.current | |
if (!el) { | |
return | |
} | |
observer.observe(el) | |
return () => { | |
observer.unobserve(el) | |
} | |
}, [target.current, enabled]) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment