mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(ui): stop Virtual Keys page from infinite render loop (#30397)
The keys-filter effect called setFilteredKeys with a fresh array on every [keys, filters] change, and the caller passed keys?.keys || [] which mints a new array identity each render when the list is empty or loading. That unstable reference re-fired the effect every render, looping until React's max update depth. The effect now bails out when the filtered result is unchanged (matching the sibling teams/orgs effects), and the caller memoizes the array it passes in.
This commit is contained in:
parent
2655d1dd5e
commit
e6423d3b74
3 changed files with 23 additions and 2 deletions
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue