From b5bf3364da0cb2da30fc36403bbad7d99183bd8f Mon Sep 17 00:00:00 2001 From: Dominik Stumpf Date: Wed, 4 Dec 2024 15:38:12 +0100 Subject: [PATCH] refactor: use tanstack query on explorer --- src/app/(dashboard)/explorer/actions.ts | 13 ---- .../explorer/components/AssociatedGuilds.tsx | 40 ++++++++++ .../components/InfiniteScrollGuilds.tsx | 15 +--- src/app/(dashboard)/explorer/fetchers.ts | 28 ++++--- src/app/(dashboard)/explorer/layout.tsx | 21 ++++++ src/app/(dashboard)/explorer/options.ts | 23 ++++++ src/app/(dashboard)/explorer/page.tsx | 74 ++----------------- src/lib/fetcher.ts | 25 ------- 8 files changed, 112 insertions(+), 127 deletions(-) delete mode 100644 src/app/(dashboard)/explorer/actions.ts create mode 100644 src/app/(dashboard)/explorer/components/AssociatedGuilds.tsx create mode 100644 src/app/(dashboard)/explorer/layout.tsx create mode 100644 src/app/(dashboard)/explorer/options.ts delete mode 100644 src/lib/fetcher.ts diff --git a/src/app/(dashboard)/explorer/actions.ts b/src/app/(dashboard)/explorer/actions.ts deleted file mode 100644 index c7f1184f5b..0000000000 --- a/src/app/(dashboard)/explorer/actions.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { fetchGuildApiData } from "@/lib/fetchGuildApi"; -import type { PaginatedResponse } from "@/lib/types"; -import type { Schemas } from "@guildxyz/types"; -import { PAGE_SIZE } from "./constants"; - -export const getGuildSearch = async ({ - pageParam, - search, -}: { pageParam: number; search: string }) => { - return fetchGuildApiData>( - `guild/search?page=${pageParam}&pageSize=${PAGE_SIZE}&search=${search}`, - ); -}; diff --git a/src/app/(dashboard)/explorer/components/AssociatedGuilds.tsx b/src/app/(dashboard)/explorer/components/AssociatedGuilds.tsx new file mode 100644 index 0000000000..2d278c316b --- /dev/null +++ b/src/app/(dashboard)/explorer/components/AssociatedGuilds.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { useSuspenseQuery } from "@tanstack/react-query"; +import { associatedGuildsOption } from "../options"; +import { CreateGuildLink } from "./CreateGuildLink"; +import { GuildCard, GuildCardSkeleton } from "./GuildCard"; + +export const AssociatedGuilds = () => { + const { data: associatedGuilds } = useSuspenseQuery(associatedGuildsOption()); + + return associatedGuilds.length > 0 ? ( +
+ {associatedGuilds.map((guild) => ( + + ))} +
+ ) : ( +
+ Guild Robot + +

+ You're not a member of any guilds yet. Explore and join some below, + or create your own! +

+ + +
+ ); +}; + +export const AssociatedGuildsSkeleton = () => { + return ( +
+ {Array.from({ length: 3 }, (_, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: + + ))} +
+ ); +}; diff --git a/src/app/(dashboard)/explorer/components/InfiniteScrollGuilds.tsx b/src/app/(dashboard)/explorer/components/InfiniteScrollGuilds.tsx index dc3df58e4b..4078a7b89d 100644 --- a/src/app/(dashboard)/explorer/components/InfiniteScrollGuilds.tsx +++ b/src/app/(dashboard)/explorer/components/InfiniteScrollGuilds.tsx @@ -4,26 +4,15 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import { useIntersection } from "foxact/use-intersection"; import { useAtomValue } from "jotai"; import { useCallback, useEffect } from "react"; -import { getGuildSearch } from "../actions"; import { searchAtom } from "../atoms"; import { PAGE_SIZE } from "../constants"; +import { guildSearchOptions } from "../options"; import { GuildCard, GuildCardSkeleton } from "./GuildCard"; export const InfiniteScrollGuilds = () => { const search = useAtomValue(searchAtom); const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = - useInfiniteQuery({ - queryKey: ["guilds", search || ""], - queryFn: ({ pageParam }) => - getGuildSearch({ search: search || "", pageParam }), - initialPageParam: 1, - staleTime: Number.POSITIVE_INFINITY, - enabled: search !== undefined, - getNextPageParam: (lastPage) => - lastPage.total / lastPage.pageSize <= lastPage.page - ? undefined - : lastPage.page + 1, - }); + useInfiniteQuery(guildSearchOptions({ search })); const [setIntersection, isIntersected, resetIsIntersected] = useIntersection({ rootMargin: "700px", diff --git a/src/app/(dashboard)/explorer/fetchers.ts b/src/app/(dashboard)/explorer/fetchers.ts index b783b5281b..f9e275d2f1 100644 --- a/src/app/(dashboard)/explorer/fetchers.ts +++ b/src/app/(dashboard)/explorer/fetchers.ts @@ -1,13 +1,21 @@ -import { env } from "@/lib/env"; -import type { Guild } from "@/lib/schemas/guild"; +import { fetchGuildApiData } from "@/lib/fetchGuildApi"; +import { tryGetParsedToken } from "@/lib/token"; import type { PaginatedResponse } from "@/lib/types"; -import { fetcher } from "../../../lib/fetcher"; +import type { Schemas } from "@guildxyz/types"; import { PAGE_SIZE } from "./constants"; -export const getGuildSearch = - (search = "") => - async ({ pageParam }: { pageParam: number }) => { - return fetcher>( - `${env.NEXT_PUBLIC_API}/guild/search?page=${pageParam}&pageSize=${PAGE_SIZE}&search=${search}`, - ); - }; +export const fetchAssociatedGuilds = async () => { + const { userId } = await tryGetParsedToken(); + return fetchGuildApiData>( + `guild/search?page=1&pageSize=${Number.MAX_SAFE_INTEGER}&sortBy=name&reverse=false&customQuery=@owner:{${userId}}`, + ); +}; + +export const fetchGuildSearch = async ({ + pageParam, + search, +}: { pageParam: number; search: string }) => { + return fetchGuildApiData>( + `guild/search?page=${pageParam}&pageSize=${PAGE_SIZE}&search=${search}`, + ); +}; diff --git a/src/app/(dashboard)/explorer/layout.tsx b/src/app/(dashboard)/explorer/layout.tsx new file mode 100644 index 0000000000..8937d8e7f1 --- /dev/null +++ b/src/app/(dashboard)/explorer/layout.tsx @@ -0,0 +1,21 @@ +import { getQueryClient } from "@/lib/getQueryClient"; +import { HydrationBoundary, dehydrate } from "@tanstack/react-query"; +import type { PropsWithChildren } from "react"; +import { associatedGuildsOption, guildSearchOptions } from "./options"; + +const ExplorerLayout = async ({ children }: PropsWithChildren) => { + const queryClient = getQueryClient(); + void queryClient.prefetchInfiniteQuery( + guildSearchOptions({}), + //queryFn: () => fetchGuildSearch({ search: "", pageParam: 1 }), + ); + void queryClient.prefetchQuery(associatedGuildsOption()); + + return ( + + {children} + + ); +}; + +export default ExplorerLayout; diff --git a/src/app/(dashboard)/explorer/options.ts b/src/app/(dashboard)/explorer/options.ts new file mode 100644 index 0000000000..95e3cad38e --- /dev/null +++ b/src/app/(dashboard)/explorer/options.ts @@ -0,0 +1,23 @@ +import { infiniteQueryOptions, queryOptions } from "@tanstack/react-query"; +import { fetchAssociatedGuilds, fetchGuildSearch } from "./fetchers"; + +export const associatedGuildsOption = () => { + return queryOptions({ + queryKey: ["associatedGuilds"], + queryFn: () => fetchAssociatedGuilds(), + select: (data) => data.items, + }); +}; + +export const guildSearchOptions = ({ search = "" }: { search?: string }) => { + return infiniteQueryOptions({ + queryKey: ["guilds", search], + queryFn: ({ pageParam }) => fetchGuildSearch({ search: search, pageParam }), + initialPageParam: 1, + enabled: search !== undefined, + getNextPageParam: (lastPage) => + lastPage.total / lastPage.pageSize <= lastPage.page + ? undefined + : lastPage.page + 1, + }); +}; diff --git a/src/app/(dashboard)/explorer/page.tsx b/src/app/(dashboard)/explorer/page.tsx index e429bbab0e..4359238757 100644 --- a/src/app/(dashboard)/explorer/page.tsx +++ b/src/app/(dashboard)/explorer/page.tsx @@ -1,37 +1,18 @@ import { AuthBoundary } from "@/components/AuthBoundary"; import { SignInButton } from "@/components/SignInButton"; -import { fetchGuildApiData } from "@/lib/fetchGuildApi"; -import { getQueryClient } from "@/lib/getQueryClient"; -import { tryGetParsedToken } from "@/lib/token"; -import type { PaginatedResponse } from "@/lib/types"; -import type { Schemas } from "@guildxyz/types"; -import { HydrationBoundary, dehydrate } from "@tanstack/react-query"; import { Suspense } from "react"; -import { getGuildSearch } from "./actions"; +import { + AssociatedGuilds, + AssociatedGuildsSkeleton, +} from "./components/AssociatedGuilds"; import { CreateGuildLink } from "./components/CreateGuildLink"; -import { GuildCard, GuildCardSkeleton } from "./components/GuildCard"; import { HeaderBackground } from "./components/HeaderBackground"; import { InfiniteScrollGuilds } from "./components/InfiniteScrollGuilds"; import { StickyNavbar } from "./components/StickyNavbar"; import { StickySearch } from "./components/StickySearch"; import { ACTIVE_SECTION } from "./constants"; -const getAssociatedGuilds = async () => { - const { userId } = await tryGetParsedToken(); - - return fetchGuildApiData>( - `guild/search?page=1&pageSize=${Number.MAX_SAFE_INTEGER}&sortBy=name&reverse=false&customQuery=@owner:{${userId}}`, - ); -}; - -export default async function Explorer() { - const queryClient = getQueryClient(); - await queryClient.prefetchInfiniteQuery({ - queryKey: ["guilds", ""], - initialPageParam: 1, - queryFn: () => getGuildSearch({ search: "", pageParam: 1 }), - }); - +const Explorer = async () => { return ( <>
- - - + ); -} +}; async function AssociatedGuildsSection() { return ( @@ -107,41 +86,4 @@ async function AssociatedGuildsSection() { ); } -async function AssociatedGuilds() { - let associatedGuilds: Schemas["Guild"][]; - try { - associatedGuilds = (await getAssociatedGuilds()).items; - } catch { - return; - } - - return associatedGuilds.length > 0 ? ( -
- {associatedGuilds.map((guild) => ( - - ))} -
- ) : ( -
- Guild Robot - -

- You're not a member of any guilds yet. Explore and join some below, - or create your own! -

- - -
- ); -} - -function AssociatedGuildsSkeleton() { - return ( -
- {Array.from({ length: 3 }, (_, i) => ( - // biome-ignore lint/suspicious/noArrayIndexKey: - - ))} -
- ); -} +export default Explorer; diff --git a/src/lib/fetcher.ts b/src/lib/fetcher.ts deleted file mode 100644 index 2a3340bc48..0000000000 --- a/src/lib/fetcher.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { env } from "./env"; -import type { ErrorLike } from "./types"; - -export const fetcher = async ( - resource: string, - requestInit: RequestInit = {}, -) => - fetch(resource, requestInit).then(async (response: Response) => { - const contentType = response.headers.get("content-type"); - const res = contentType?.includes("json") - ? await response.json() - : await response.text(); - - if (!response.ok) { - if (resource.includes(env.NEXT_PUBLIC_API)) { - return Promise.reject({ - error: (res as ErrorLike).error || (res as ErrorLike).message, - }); - } - - return Promise.reject(res as Error); - } - - return res as Data; - });