From c8496a2767e9f19ba24110a77d48e33895daaa75 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Wed, 15 Apr 2026 12:55:08 -0700 Subject: [PATCH] fix(ui): invalidate organization queries after team mutations Team create/delete/update can change which teams belong to an organization (via teamCreateCall, teamDeleteCall, and teamUpdateCall when organization_id changes). Without invalidating the React Query cache for organizations, the org info page's teams list and any useOrganizations() consumer (e.g. the team edit form's organization dropdown) stay stale until a hard reload. Export organizationKeys from useOrganizations and invalidate organizationKeys.all after each successful team mutation, matching the pattern used in hooks/projects, hooks/budgets, and hooks/accessGroups. --- .../app/(dashboard)/hooks/organizations/useOrganizations.ts | 2 +- ui/litellm-dashboard/src/app/(dashboard)/teams/TeamsView.tsx | 4 ++++ .../(dashboard)/teams/components/modals/CreateTeamModal.tsx | 4 ++++ ui/litellm-dashboard/src/components/team/TeamInfo.tsx | 5 ++++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts index 323270f4360..0e7cd8342ec 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/organizations/useOrganizations.ts @@ -3,7 +3,7 @@ import { Organization, organizationInfoCall, organizationListCall } from "@/comp import { useQuery, useQueryClient, UseQueryResult } from "@tanstack/react-query"; import { createQueryKeys } from "../common/queryKeysFactory"; -const organizationKeys = createQueryKeys("organizations"); +export const organizationKeys = createQueryKeys("organizations"); export const useOrganizations = (): UseQueryResult => { const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/TeamsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/TeamsView.tsx index fcad42d3a75..94a0e03304e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/teams/TeamsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/TeamsView.tsx @@ -1,4 +1,6 @@ import React, { useState, useEffect } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { organizationKeys } from "@/app/(dashboard)/hooks/organizations/useOrganizations"; import { teamDeleteCall, Organization } from "@/components/networking"; import { fetchTeams } from "@/components/common_components/fetch_teams"; import { Form } from "antd"; @@ -54,6 +56,7 @@ const TeamsView: React.FC = ({ organizations, premiumUser = false, }) => { + const queryClient = useQueryClient(); const [currentOrg, setCurrentOrg] = useState(null); const [showFilters, setShowFilters] = useState(false); const [filters, setFilters] = useState({ @@ -138,6 +141,7 @@ const TeamsView: React.FC = ({ try { await teamDeleteCall(accessToken, teamToDelete); + queryClient.invalidateQueries({ queryKey: organizationKeys.all }); // Successfully completed the deletion. Update the state to trigger a rerender. fetchTeams(accessToken, userID, userRole, currentOrg, setTeams); } catch (error) { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx index ecaa3c08a41..f42371f83a5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx @@ -13,9 +13,11 @@ import AgentSelector from "@/components/agent_management/AgentSelector"; import PremiumLoggingSettings from "@/components/common_components/PremiumLoggingSettings"; import ModelAliasManager from "@/components/common_components/ModelAliasManager"; import React, { useEffect, useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; import NotificationsManager from "@/components/molecules/notifications_manager"; import { fetchMCPAccessGroups, getGuardrailsList, getPoliciesList, Organization, Team, teamCreateCall } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { organizationKeys } from "@/app/(dashboard)/hooks/organizations/useOrganizations"; import MCPToolPermissions from "@/components/mcp_server_management/MCPToolPermissions"; interface ModelAliases { @@ -71,6 +73,7 @@ const CreateTeamModal = ({ setIsTeamModalVisible, }: CreateTeamModalProps) => { const { userId: userID, userRole, accessToken, premiumUser } = useAuthorized(); + const queryClient = useQueryClient(); const [form] = Form.useForm(); const [userModels, setUserModels] = useState([]); const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState(null); @@ -273,6 +276,7 @@ const CreateTeamModal = ({ } const response: any = await teamCreateCall(accessToken, formValues); + queryClient.invalidateQueries({ queryKey: organizationKeys.all }); if (teams !== null) { setTeams([...teams, response]); } else { diff --git a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx index 65aa74b272b..254d46e978b 100644 --- a/ui/litellm-dashboard/src/components/team/TeamInfo.tsx +++ b/ui/litellm-dashboard/src/components/team/TeamInfo.tsx @@ -1,5 +1,6 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; -import { useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations"; +import { organizationKeys, useOrganizations } from "@/app/(dashboard)/hooks/organizations/useOrganizations"; +import { useQueryClient } from "@tanstack/react-query"; import UserSearchModal from "@/components/common_components/user_search_modal"; import { getPoliciesList, @@ -195,6 +196,7 @@ const TeamInfoView: React.FC = ({ const [organization, setOrganization] = useState(null); const { userRole, userId } = useAuthorized(); const { data: userOrganizations = [] } = useOrganizations(); + const queryClient = useQueryClient(); // Check if user is org admin for this team's organization const isOrgAdminForTeam = useMemo(() => { @@ -611,6 +613,7 @@ const TeamInfoView: React.FC = ({ } const response = await teamUpdateCall(accessToken, updateData); + queryClient.invalidateQueries({ queryKey: organizationKeys.all }); NotificationsManager.success("Team settings updated successfully"); setIsEditing(false);