From c7d203366c90b941665a1911e049e9277a1cd796 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 28 Feb 2026 17:44:09 -0800 Subject: [PATCH 1/2] [Fix] UI - QueryClient: move to single root-level provider A new QueryClient() was instantiated inside 5 component render functions and at module-level in 3 more pages, with no shared QueryClientProvider in either layout. This caused isolated, ephemeral caches with no cross-page sharing and cache destruction on every re-render. Moves QueryClient to a single module-level constant in AntdGlobalProvider (the existing root-level "use client" wrapper in app/layout.tsx) and removes the per-page QueryClient instantiations and QueryClientProvider wrappers from all 8 affected pages. Co-Authored-By: Claude Sonnet 4.6 --- .../src/app/(dashboard)/logs/page.tsx | 21 +++++------ .../(dashboard)/tools/mcp-servers/page.tsx | 9 +---- .../src/app/(dashboard)/users/page.tsx | 22 +++++------- .../src/app/(dashboard)/virtual-keys/page.tsx | 35 ++++++++----------- .../src/app/login/LoginPage.tsx | 9 +---- .../src/app/model_hub_table/page.tsx | 7 +--- .../src/app/onboarding/page.tsx | 19 ++++------ ui/litellm-dashboard/src/app/page.tsx | 7 +--- .../src/contexts/AntdGlobalProvider.tsx | 7 ++-- 9 files changed, 48 insertions(+), 88 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx index b04a12e230..f93b34fbdc 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/logs/page.tsx @@ -3,25 +3,20 @@ import SpendLogsTable from "@/components/view_logs"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import useTeams from "@/app/(dashboard)/hooks/useTeams"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const LogsPage = () => { const { accessToken, token, userRole, userId, premiumUser } = useAuthorized(); const { teams } = useTeams(); - const queryClient = new QueryClient(); - return ( - - - + ); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/tools/mcp-servers/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/tools/mcp-servers/page.tsx index 1bea7ac74a..9b94de6c9f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/tools/mcp-servers/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/tools/mcp-servers/page.tsx @@ -2,18 +2,11 @@ import { MCPServers } from "@/components/mcp_tools"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const MCPServersPage = () => { const { accessToken, userRole, userId } = useAuthorized(); - const queryClient = new QueryClient(); - - return ( - - - - ); + return ; }; export default MCPServersPage; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/page.tsx index 7cf401873d..5ab6920b28 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/page.tsx @@ -4,27 +4,23 @@ import ViewUserDashboard from "@/components/view_users"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import useTeams from "@/app/(dashboard)/hooks/useTeams"; import { useState } from "react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const UsersPage = () => { const { accessToken, userRole, userId, token } = useAuthorized(); const [keys, setKeys] = useState([]); const { teams } = useTeams(); - const queryClient = new QueryClient(); return ( - - - + ); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/virtual-keys/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/virtual-keys/page.tsx index 856f552e93..226616474e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/virtual-keys/page.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/virtual-keys/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import useKeyList from "@/components/key_team_helpers/key_list"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import UserDashboard from "@/components/user_dashboard"; import useTeams from "@/app/(dashboard)/hooks/useTeams"; @@ -14,8 +13,6 @@ const VirtualKeysPage = () => { const [createClicked, setCreateClicked] = useState(false); const [organizations, setOrganizations] = useState([]); - const queryClient = new QueryClient(); - const { keys, isLoading, error, pagination, refresh, setKeys } = useKeyList({ selectedKeyAlias: null, currentOrg: null, @@ -29,23 +26,21 @@ const VirtualKeysPage = () => { }; return ( - - {}} - setUserEmail={() => {}} - setTeams={setTeams} - setKeys={setKeys} - premiumUser={premiumUser} - organizations={organizations} - addKey={addKey} - createClicked={createClicked} - /> - + {}} + setUserEmail={() => {}} + setTeams={setTeams} + setKeys={setKeys} + premiumUser={premiumUser} + organizations={organizations} + addKey={addKey} + createClicked={createClicked} + /> ); }; diff --git a/ui/litellm-dashboard/src/app/login/LoginPage.tsx b/ui/litellm-dashboard/src/app/login/LoginPage.tsx index a05fa4e214..2a2915ea35 100644 --- a/ui/litellm-dashboard/src/app/login/LoginPage.tsx +++ b/ui/litellm-dashboard/src/app/login/LoginPage.tsx @@ -7,7 +7,6 @@ import { getProxyBaseUrl } from "@/components/networking"; import { getCookie } from "@/utils/cookieUtils"; import { isJwtExpired } from "@/utils/jwtUtils"; import { InfoCircleOutlined } from "@ant-design/icons"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Alert, Button, Card, Form, Input, Popover, Space, Typography } from "antd"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; @@ -218,11 +217,5 @@ function LoginPageContent() { } export default function LoginPage() { - const queryClient = new QueryClient(); - - return ( - - - - ); + return ; } diff --git a/ui/litellm-dashboard/src/app/model_hub_table/page.tsx b/ui/litellm-dashboard/src/app/model_hub_table/page.tsx index 3f14c4fc3f..f35a6943a6 100644 --- a/ui/litellm-dashboard/src/app/model_hub_table/page.tsx +++ b/ui/litellm-dashboard/src/app/model_hub_table/page.tsx @@ -2,9 +2,6 @@ import React, { Suspense, useEffect, useState } from "react"; import { useSearchParams } from "next/navigation"; import ModelHubTable from "@/components/AIHub/ModelHubTable"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; - -const queryClient = new QueryClient(); function PublicModelHubTableContent() { const searchParams = useSearchParams()!; @@ -20,9 +17,7 @@ function PublicModelHubTableContent() { }, [key]); return ( - - - + ); } diff --git a/ui/litellm-dashboard/src/app/onboarding/page.tsx b/ui/litellm-dashboard/src/app/onboarding/page.tsx index f424c9e628..d7840a7be3 100644 --- a/ui/litellm-dashboard/src/app/onboarding/page.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/page.tsx @@ -1,11 +1,8 @@ "use client"; import React, { Suspense } from "react"; import { useSearchParams } from "next/navigation"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { OnboardingForm } from "./OnboardingForm"; -const queryClient = new QueryClient(); - function OnboardingContent() { const searchParams = useSearchParams()!; const action = searchParams.get("action"); @@ -15,14 +12,12 @@ function OnboardingContent() { export default function Onboarding() { return ( - - Loading... - } - > - - - + Loading... + } + > + + ); } diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 0b2f467e8f..622c3bf70a 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -45,7 +45,6 @@ import ViewUserDashboard from "@/components/view_users"; import { ThemeProvider } from "@/contexts/ThemeContext"; import { isJwtExpired } from "@/utils/jwtUtils"; import { isAdminRole } from "@/utils/roles"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { jwtDecode } from "jwt-decode"; import { useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; @@ -103,8 +102,6 @@ interface ProxySettings { LITELLM_UI_API_DOC_BASE_URL?: string | null; } -const queryClient = new QueryClient(); - function CreateKeyPageContent() { const [userRole, setUserRole] = useState(""); const [premiumUser, setPremiumUser] = useState(false); @@ -372,8 +369,7 @@ function CreateKeyPageContent() { return ( }> - - @@ -600,7 +596,6 @@ function CreateKeyPageContent() { )} - ); } diff --git a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx index 5b5c1036fa..2e6d036dfd 100644 --- a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx +++ b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx @@ -2,8 +2,11 @@ import React, { useEffect, useRef } from "react"; import { notification } from "antd"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { setNotificationInstance } from "@/components/molecules/notifications_manager"; +const queryClient = new QueryClient(); + export default function AntdGlobalProvider({ children }: { children: React.ReactNode }) { const [api, contextHolder] = notification.useNotification(); const initialized = useRef(false); @@ -16,9 +19,9 @@ export default function AntdGlobalProvider({ children }: { children: React.React }, [api]); return ( - <> + {contextHolder} {children} - + ); } From 8269f319215a1eea2a20381491fe610d4f03587b Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Sat, 28 Feb 2026 17:47:47 -0800 Subject: [PATCH 2/2] refactor: extract ReactQueryProvider into dedicated context file Co-Authored-By: Claude Sonnet 4.6 --- ui/litellm-dashboard/src/app/layout.tsx | 5 ++++- ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx | 7 ++----- ui/litellm-dashboard/src/contexts/ReactQueryProvider.tsx | 9 +++++++++ 3 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 ui/litellm-dashboard/src/contexts/ReactQueryProvider.tsx diff --git a/ui/litellm-dashboard/src/app/layout.tsx b/ui/litellm-dashboard/src/app/layout.tsx index 1233da9046..a4ed17cde3 100644 --- a/ui/litellm-dashboard/src/app/layout.tsx +++ b/ui/litellm-dashboard/src/app/layout.tsx @@ -3,6 +3,7 @@ import { Inter } from "next/font/google"; import "./globals.css"; import AntdGlobalProvider from "@/contexts/AntdGlobalProvider"; +import ReactQueryProvider from "@/contexts/ReactQueryProvider"; const inter = Inter({ subsets: ["latin"] }); @@ -20,7 +21,9 @@ export default function RootLayout({ return ( - {children} + + {children} + ); diff --git a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx index 2e6d036dfd..5b5c1036fa 100644 --- a/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx +++ b/ui/litellm-dashboard/src/contexts/AntdGlobalProvider.tsx @@ -2,11 +2,8 @@ import React, { useEffect, useRef } from "react"; import { notification } from "antd"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { setNotificationInstance } from "@/components/molecules/notifications_manager"; -const queryClient = new QueryClient(); - export default function AntdGlobalProvider({ children }: { children: React.ReactNode }) { const [api, contextHolder] = notification.useNotification(); const initialized = useRef(false); @@ -19,9 +16,9 @@ export default function AntdGlobalProvider({ children }: { children: React.React }, [api]); return ( - + <> {contextHolder} {children} - + ); } diff --git a/ui/litellm-dashboard/src/contexts/ReactQueryProvider.tsx b/ui/litellm-dashboard/src/contexts/ReactQueryProvider.tsx new file mode 100644 index 0000000000..cf2d203a82 --- /dev/null +++ b/ui/litellm-dashboard/src/contexts/ReactQueryProvider.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +const queryClient = new QueryClient(); + +export default function ReactQueryProvider({ children }: { children: React.ReactNode }) { + return {children}; +}