diff --git a/bun.lock b/bun.lock
index b1b5436..2de0fbb 100644
--- a/bun.lock
+++ b/bun.lock
@@ -34,6 +34,7 @@
"@eslint/eslintrc": "^3",
"@next/eslint-plugin-next": "15.5.2",
"@tailwindcss/postcss": "^4",
+ "@tanstack/react-query-devtools": "^5.90.2",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20",
"@types/react": "19.1.12",
@@ -287,8 +288,12 @@
"@tanstack/query-core": ["@tanstack/query-core@5.90.2", "", {}, "sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ=="],
+ "@tanstack/query-devtools": ["@tanstack/query-devtools@5.90.1", "", {}, "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ=="],
+
"@tanstack/react-query": ["@tanstack/react-query@5.90.2", "", { "dependencies": { "@tanstack/query-core": "5.90.2" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw=="],
+ "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.90.2", "", { "dependencies": { "@tanstack/query-devtools": "5.90.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.2", "react": "^18 || ^19" } }, "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ=="],
+
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
diff --git a/package.json b/package.json
index f07f8be..e5786b3 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"@eslint/eslintrc": "^3",
"@next/eslint-plugin-next": "15.5.2",
"@tailwindcss/postcss": "^4",
+ "@tanstack/react-query-devtools": "^5.90.2",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20",
"@types/react": "19.1.12",
diff --git a/src/app/(stats)/guild/[value]/_components/members.tsx b/src/app/(stats)/guild/[value]/_components/members.tsx
index 52ac54d..4a6a288 100644
--- a/src/app/(stats)/guild/[value]/_components/members.tsx
+++ b/src/app/(stats)/guild/[value]/_components/members.tsx
@@ -7,10 +7,11 @@ import { formatDate, formatNumber } from "@/lib/formatters"
import { head } from "@/lib/hypixel/general"
import { Guild } from "@/lib/schema/guild"
import { playerForGuildSchema } from "@/lib/schema/player"
-import { QueryClient, QueryClientProvider, useQuery } from "@tanstack/react-query"
+import { ApiResponse } from "@/types"
+import { useQueries } from "@tanstack/react-query"
import Image from "next/image"
import Link from "next/link"
-import { useCallback, useEffect, useRef, useState } from "react"
+import { useEffect, useRef } from "react"
import { toast } from "sonner"
import z from "zod"
@@ -21,68 +22,27 @@ type MemberWithPlayer = Guild["guild"]["members"][number] & {
error?: boolean
}
-const queryClient = new QueryClient()
-
export function GuildMembers({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) {
- return (
-
-
-
- )
-}
-
-function useMemberData(uuid: string) {
- return useQuery({
- queryKey: ["guildMember", uuid],
- queryFn: async () => {
- const response = await fetch(`/api/guildmembers?uuid=${uuid}`)
- const data = await response.json()
-
- if (data.error) {
- throw new Error(data.message || "Failed to fetch member data")
- }
-
- return data.player as PlayerForGuild["player"]
- },
- staleTime: 1000 * 60 * 60 * 24,
- gcTime: 1000 * 60 * 60,
- retry: 3,
- retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000)
- })
-}
-
-function MemberRow({ member, onLoad }: { member: Guild["guild"]["members"][number], onLoad: () => void }) {
- const { data: player, isLoading, isError } = useMemberData(member.uuid)
-
- useEffect(() => {
- if (!isLoading && (player || isError)) {
- onLoad()
- }
- }, [isLoading, player, isError, onLoad])
-
- if (isLoading || (!player && !isError)) return null
- if (isError || !player) return null
-
- const memberWithPlayer: MemberWithPlayer = {
- ...member,
- player,
- loading: isLoading,
- error: isError
- }
-
- return
-}
-
-function GuildMembersInternal({ members, ranks }: { members: Guild["guild"]["members"], ranks: Guild["guild"]["ranks"] }) {
- const [loadedCount, setLoadedCount] = useState(0)
const hasShownToast = useRef(false)
-
const totalMembers = members.length
const TOAST_ID = "guild.members.progress"
- const handleMemberLoad = useCallback(() => {
- setLoadedCount(prev => prev + 1)
- }, [])
+ const memberQueries = useQueries({
+ queries: members.map(member => ({
+ queryKey: ["guildMember", member.uuid],
+ queryFn: async () => {
+ const response = await fetch(`/api/guildmembers?uuid=${member.uuid}`)
+ const data = await response.json() as ApiResponse
+ if (data.error) {
+ throw new Error(data.message)
+ }
+ return data.player
+ },
+ staleTime: 1000 * 60 * 60 * 24
+ }))
+ })
+
+ const loadedCount = memberQueries.filter(query => !query.isLoading && (query.data || query.isError)).length
const sortedMembers = [...members].sort((a, b) => {
if (a.rank === "Guild Master" && b.rank !== "Guild Master") return -1
@@ -139,13 +99,21 @@ function GuildMembersInternal({ members, ranks }: { members: Guild["guild"]["mem
- {sortedMembers.map(member => (
-
- ))}
+ {sortedMembers.map((member) => {
+ const query = memberQueries[members.findIndex(m => m.uuid === member.uuid)]
+
+ if (query.isLoading || (!query.data && !query.isError)) return null
+ if (query.isError || !query.data) return null
+
+ const memberWithPlayer: MemberWithPlayer = {
+ ...member,
+ player: query.data,
+ loading: query.isLoading,
+ error: query.isError
+ }
+
+ return
+ })}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 220f6dc..650428a 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,10 +1,11 @@
import "./globals.css"
import { GeistSans as geist } from "geist/font/sans"
+import type { Metadata } from "next"
+import { QueryClientProvider, ReactQueryDevtools } from "@/components/query-provider"
import ThemeProvider from "@/components/theme-provider"
import { Toaster } from "@/components/ui/sonner"
-import type { Metadata } from "next"
export const metadata: Metadata = {
title: {
@@ -21,10 +22,13 @@ export default function RootLayout({ children }: LayoutProps<"/">) {
{/* {process.env.NODE_ENV === "development" && } */}
-
- {children}
-
-
+
+
+ {children}
+
+
+
+