diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx index 295638545c9..299f8a05f71 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx @@ -96,6 +96,8 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo // Use the filter logic hook + const keyList = useMemo(() => keys?.keys ?? [], [keys]); + const { filters, filteredKeys, @@ -105,7 +107,7 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo handleFilterChange, handleFilterReset, } = useFilterLogic({ - keys: keys?.keys || [], + keys: keyList, teams, organizations, }); diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx index 24891ac6615..23259528687 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx @@ -149,6 +149,23 @@ describe("useFilterLogic – filteredTotalCount", () => { expect(result.current.filteredTotalCount).toBeNull(); }); + it("should not enter an infinite update loop when keys is a fresh array reference on every render", () => { + const sourceKeys = [mockKey]; + let renderCount = 0; + + const { result } = renderHook(() => { + renderCount += 1; + const value = useFilterLogic({ keys: [...sourceKeys], teams: [], organizations: [] }); + if (renderCount > 25) { + throw new Error(`useFilterLogic re-rendered ${renderCount} times; setFilteredKeys is looping`); + } + return value; + }); + + expect(result.current.filteredKeys).toEqual([mockKey]); + expect(renderCount).toBeLessThanOrEqual(25); + }); + it("should not trigger a debounced search when skipDebounce is true", async () => { const { result } = renderHook(() => useFilterLogic(defaultProps)); diff --git a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx index cd55477208c..e31a6fbee38 100644 --- a/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx +++ b/ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.tsx @@ -99,7 +99,9 @@ export function useFilterLogic({ result = result.filter((key) => (key.organization_id ?? key.org_id) === filters["Organization ID"]); } - setFilteredKeys(result); + setFilteredKeys((prev) => + prev.length === result.length && prev.every((key, index) => key === result[index]) ? prev : result, + ); }, [keys, filters]); // Fetch all data for filters when component mounts