Skip to content

Commit

Permalink
refactor: use react-query for infinite fetch scrolling
Browse files Browse the repository at this point in the history
  • Loading branch information
SwiichyCode committed Mar 5, 2024
1 parent 4bee1b6 commit e26257f
Show file tree
Hide file tree
Showing 17 changed files with 219 additions and 82 deletions.
52 changes: 33 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@radix-ui/react-toggle-group": "^1.0.4",
"@t3-oss/env-nextjs": "^0.7.1",
"@tanstack/react-query": "^5.24.1",
"@tanstack/react-query-devtools": "^5.24.8",
"@tanstack/react-query-next-experimental": "^5.24.1",
"@tiptap/react": "^2.2.4",
"@tiptap/starter-kit": "^2.2.4",
Expand Down
4 changes: 4 additions & 0 deletions src/actions/getRepositories.action.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
"use server";
import repositoryService from "@/services/repository.service";
import { revalidatePath } from "next/cache";

type Props = {
query?: string;
language?: string;
offset?: number;
limit?: number;
cursor?: number;
};

export const getRepositoriesOnScroll = async ({
query,
language,
offset = 0,
limit = 20,
cursor,
}: Props) => {
return await repositoryService.getRepositoriesOnScroll({
query,
language,
offset,
limit,
cursor,
Expand Down
File renamed without changes.
8 changes: 7 additions & 1 deletion src/app/(app)/repositories/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ export default async function RepositoriesPage({ searchParams }: Props) {
return (
<RepositoriesProvider user={user} data={data} likes={likes}>
<RepositoriesFilter languages={languages} />
<RepositoriesGridInfiniteScroll query={query} language={language} />

<RepositoriesGridInfiniteScroll
user={user}
query={query}
language={language}
/>

<DataSharingAgreementForm user={user} />
<AddRepositoryForm />
</RepositoriesProvider>
Expand Down
2 changes: 1 addition & 1 deletion src/components/molecules/InputSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const InputSearch = ({ placeholder }: Props) => {
"hidden h-8 w-full bg-transparent focus:border-0 focus:outline-none focus:ring-0",
"lg:flex",
)}
placeholder={placeholder ? placeholder : "Type to search..."}
placeholder={placeholder ? placeholder : "Repository name..."}
onChange={handleSearch}
onFocus={() => setIsEditing(true)}
onBlur={() => setIsEditing(false)}
Expand Down
33 changes: 33 additions & 0 deletions src/components/molecules/PrefetchLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use client";
import Link from "next/link";
import { useQueryClient } from "@tanstack/react-query";
import { getRepositoriesOnScroll } from "@/actions/getRepositories.action";
import { URL } from "@/constants";

export const PrefetchLink = () => {
const queryClient = useQueryClient();

const prefetchRepositories = () => {
queryClient
.prefetchInfiniteQuery({
queryKey: ["repositories", { query: "", language: "" }],
queryFn: ({ pageParam }) =>
getRepositoriesOnScroll({ cursor: pageParam }),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
pages: 1,
staleTime: 1000 * 60 * 5,
})
.catch(console.error);
};

return (
<Link
className="rounded-md bg-indigo-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400"
href={URL.REPOSITORIES}
onMouseEnter={prefetchRepositories}
>
Get started
</Link>
);
};
Original file line number Diff line number Diff line change
@@ -1,52 +1,46 @@
"use client";
import { useInfiniteScroll } from "@/hooks/useInfiniteScroll";

import { useFetchInfiniteRepositories } from "@/hooks/useFetchInfiniteRepositories";
import { useFetchNextPage } from "@/hooks/useFetchNextPage";
import { RepositoryCard } from "@/components/organisms/RepositoryCard/_index";
import { RepositoriesLoader } from "@/components/organisms/RepositoriesGrid/RepositoriesLoader";
import { RepositoriesGridLayout } from "./RepositoriesGridLayout";
import { useRepositoriesContext } from "@/context/repositoriesContext";
import { useToggleFilter } from "@/stores/useToggleFilter";
import { getFilteredRepositories } from "@/utils/getFilteredRepositories";
import { User } from "@/types/prisma.type";

type Props = {
user: User | null;
query: string;
language: string;
};

export const RepositoriesGridInfiniteScroll = ({ query, language }: Props) => {
const { data: initialRepositories, user } = useRepositoriesContext();
const { repositories, repositoriesAlreadyStarred, ref, isDisabled } =
useInfiniteScroll({
initialRepositories,
limit: 20,
user,
});

const { toggleFilter } = useToggleFilter();
export const RepositoriesGridInfiniteScroll = ({
user,
query,
language,
}: Props) => {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useFetchInfiniteRepositories({ query, language });

const filteredRepositories = getFilteredRepositories({
query,
language,
initialRepositories,
repositories,
user,
toggleFilter,
const { ref } = useFetchNextPage({
action: fetchNextPage,
hasNextPage,
});

console.log("rerendering RepositoriesGridInfiniteScroll");

return (
<>
<RepositoriesGridLayout>
{filteredRepositories.map((repository) => (
<RepositoryCard
key={repository.id}
user={user}
repository={repository}
repositoriesAlreadyStarred={repositoriesAlreadyStarred}
/>
))}
</RepositoriesGridLayout>
<RepositoriesLoader isDisabled={isDisabled} ref={ref} />
{data?.pages.map((page, i) => (
<RepositoriesGridLayout key={i}>
{page.data.map((repository) => (
<RepositoryCard
key={repository.id}
user={user}
repository={repository}
// repositoriesAlreadyStarred={repositoriesAlreadyStarred}
/>
))}
</RepositoriesGridLayout>
))}
<RepositoriesLoader isDisabled={isFetchingNextPage} ref={ref} />
</>
);
};
12 changes: 8 additions & 4 deletions src/components/organisms/RepositoriesGrid/RepositoriesLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { forwardRef, memo } from "react";
import { forwardRef } from "react";

type Props = {
isDisabled: boolean;
isDisabled?: boolean;
};

const RepositoriesLoader = forwardRef(
(props: Props, ref: React.ForwardedRef<HTMLDivElement | null>) => {
const { isDisabled } = props;

return isDisabled ? null : (
<div ref={ref} className="mt-6 flex flex-col items-center justify-center">
return (
<div
ref={ref}
className="mt-6 flex flex-col items-center justify-center"
style={{ visibility: !isDisabled ? "hidden" : "initial" }}
>
<p className="text-sm text-gray-400">Loading more repositories...</p>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const RepositoryCardHeader = ({
pictureUrl={`${repository.ownerAvatarUrl}?size=40`}
alt={repository.ownerUsername}
/>

<div className="flex flex-col">
<Link
href={repository.url}
Expand Down
10 changes: 2 additions & 8 deletions src/components/templates/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Link from "next/link";
import Image from "next/image";
import { URL } from "@/constants";
import { PrefetchLink } from "../molecules/PrefetchLink";

export const Hero = () => {
return (
Expand Down Expand Up @@ -29,12 +28,7 @@ export const Hero = () => {
and more engaging. Start exploring now!
</p>
<div className="mt-10 flex items-center justify-center gap-x-6">
<Link
className="rounded-md bg-indigo-500 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-400"
href={URL.REPOSITORIES}
>
Get started
</Link>
<PrefetchLink />
<a
href="#faqs"
className="text-sm font-semibold leading-6 text-white"
Expand Down
20 changes: 20 additions & 0 deletions src/hooks/useFetchInfiniteRepositories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useSuspenseInfiniteQuery } from "@tanstack/react-query";
import { getRepositoriesOnScroll } from "@/actions/getRepositories.action";

type Props = {
query: string;
language: string;
};

export const useFetchInfiniteRepositories = ({ query, language }: Props) => {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } =
useSuspenseInfiniteQuery({
queryKey: ["repositories", { query, language }],
queryFn: ({ pageParam }) =>
getRepositoriesOnScroll({ cursor: pageParam }),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor,
});

return { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading };
};
26 changes: 26 additions & 0 deletions src/hooks/useFetchNextPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {
FetchNextPageOptions,
InfiniteData,
InfiniteQueryObserverResult,
} from "@tanstack/react-query";
import { useEffect } from "react";
import { useInView } from "react-intersection-observer";

type Props = {
action: (
options?: FetchNextPageOptions | undefined,
) => Promise<InfiniteQueryObserverResult<InfiniteData<any>, Error>>;
hasNextPage: boolean;
};

export const useFetchNextPage = ({ action, hasNextPage }: Props) => {
const { ref, inView } = useInView();

useEffect(() => {
if (inView && hasNextPage) {
void action();
}
}, [inView]);

return { ref, inView };
};
9 changes: 6 additions & 3 deletions src/providers/TanstackProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React, { type PropsWithChildren, useState } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryStreamedHydration } from "@tanstack/react-query-next-experimental";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

export const TanstackProvider = ({ children }: PropsWithChildren) => {
const [queryClient] = useState(
Expand All @@ -10,16 +11,18 @@ export const TanstackProvider = ({ children }: PropsWithChildren) => {
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 1000 * 60 * 5, // 5 minutes
refetchOnMount: false,
staleTime: 300000,
},
},
}),
);

return (
<QueryClientProvider client={queryClient}>
<ReactQueryStreamedHydration>{children}</ReactQueryStreamedHydration>
<ReactQueryStreamedHydration>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</ReactQueryStreamedHydration>
</QueryClientProvider>
);
};
Loading

0 comments on commit e26257f

Please sign in to comment.