Skip to content

fix(ui): prevent infinite re-render loop in VirtualKeysTable - #26601

Open
Bytechoreographer wants to merge 4 commits into
BerriAI:litellm_internal_stagingfrom
Bytechoreographer:fix/virtual-keys-infinite-rerender-oss
Open

fix(ui): prevent infinite re-render loop in VirtualKeysTable#26601
Bytechoreographer wants to merge 4 commits into
BerriAI:litellm_internal_stagingfrom
Bytechoreographer:fix/virtual-keys-infinite-rerender-oss

Conversation

@Bytechoreographer

Copy link
Copy Markdown
Contributor

PR: fix(ui): prevent infinite re-render loop in VirtualKeysTable

Relevant issues

Pre-Submission checklist

  • Added tests in ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx
  • npm run test passes for affected files (34/34)
  • Scope is isolated: one source file changed, one test file updated
  • Comment @greptileai on the PR and get Confidence Score ≥ 4/5 before requesting maintainer review

Type

🐛 Bug Fix

Changes

Root cause

VirtualKeysTable.tsx passed keys?.keys || [] directly into useFilterLogic:

useFilterLogic({
  keys: keys?.keys || [],   // ← new [] literal on every render
  ...
})

While keys is loading (keys?.keys is undefined), the || [] fallback
produces a new array reference on every render. The useEffect([keys, filters])
inside useFilterLogic treats each new reference as a change, calls
setFilteredKeys, which triggers a re-render, which produces another new []
resulting in an infinite loop.

In development mode this surfaces as:

Maximum update depth exceeded.
src/components/key_team_helpers/filter_logic.tsx (102:5) @ useFilterLogic.useEffect

In the production build React's production runtime silences the warning, but
the excess re-renders still occur on every page load.

Fix

Stabilise the reference with useMemo before passing it to the hook
(VirtualKeysTable.tsx):

// Before
useFilterLogic({
  keys: keys?.keys || [],
  ...
})

// After
const keysList = useMemo(() => keys?.keys ?? [], [keys?.keys]);

useFilterLogic({
  keys: keysList,
  ...
})

useMemo returns the same [] across renders until keys?.keys actually
changes, breaking the loop. ?? is used instead of || to correctly handle
null as well as undefined.

Files changed

File Change
ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx Add useMemo to stabilise keys reference
ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx Add 2 regression tests

Tests added (filter_logic.test.tsx)

Test What it verifies
should not enter an infinite render loop when keys prop is re-rendered with a new empty-array reference Simulates the || [] pattern: calls rerender({ keys: [] }) three times; the hook must complete without hanging
should update filteredKeys when keys prop changes from empty to populated Verifies filteredKeys correctly reflects new data when keys transitions from [] to a populated array

`keys?.keys || []` produces a new array reference on every render when
`keys` is undefined (during initial load).  The useEffect([keys, filters])
in useFilterLogic treated each new reference as a change, called
setFilteredKeys, triggered a re-render, and looped indefinitely.

Stabilise the reference with useMemo before passing it to the hook:

  const keysList = useMemo(() => keys?.keys ?? [], [keys?.keys]);

Also add two regression tests in filter_logic.test.tsx that verify
the hook does not hang when re-rendered with a new empty-array reference.

Co-Authored-By: Claude Sonnet 4 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes an infinite re-render loop in VirtualKeysTable by wrapping keys?.keys in useMemo before passing it to useFilterLogic. The root cause was that keys?.keys || [] produced a new array reference on every render while keys was loading, causing useFilterLogic's useEffect([keys, filters]) to fire continuously and set state that triggered another render. Two regression tests are added for useFilterLogic in isolation.

Confidence Score: 4/5

Safe to merge — the fix is minimal, correct, and well-scoped; only a test coverage gap remains.

The useMemo fix is correct and directly addresses the described root cause. The only finding is a P2: the new stability test validates useFilterLogic in isolation but doesn't exercise the consumer-side useMemo fix that actually breaks the loop in production, so it would pass even if the fix were reverted.

No files require special attention.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx Wraps keys?.keys in useMemo to stabilise the array reference passed to useFilterLogic, correctly breaking the infinite render loop triggered by `
ui/litellm-dashboard/src/components/key_team_helpers/filter_logic.test.tsx Adds two regression tests for useFilterLogic stability; tests validate hook isolation but don't fully exercise the consumer-side useMemo fix that breaks the actual render loop.

Sequence Diagram

sequenceDiagram
    participant VKT as VirtualKeysTable
    participant useMemo
    participant UFL as useFilterLogic (useEffect)
    participant State as filteredKeys state

    Note over VKT,State: BEFORE fix — infinite loop while keys is loading

    VKT->>UFL: keys = [] (ref A, new literal)
    UFL->>State: setFilteredKeys([])
    State-->>VKT: re-render triggered
    VKT->>UFL: keys = [] (ref B, new literal)
    UFL->>State: setFilteredKeys([])
    State-->>VKT: re-render triggered
    Note over VKT: Maximum update depth exceeded

    Note over VKT,State: AFTER fix — loop broken by useMemo

    VKT->>useMemo: keys?.keys = undefined
    useMemo-->>VKT: stable [] (ref A, same reference)
    VKT->>UFL: keys = [] (ref A)
    UFL->>State: setFilteredKeys([])
    State-->>VKT: re-render triggered
    VKT->>useMemo: keys?.keys = undefined (unchanged)
    useMemo-->>VKT: stable [] (ref A, same reference)
    VKT->>UFL: keys = [] (ref A, no change)
    Note over UFL: useEffect skips — deps unchanged
Loading

Reviews (1): Last reviewed commit: "fix(ui): prevent infinite re-render loop..." | Re-trigger Greptile

@codecov

codecov Bot commented Apr 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Bytechoreographer

Copy link
Copy Markdown
Contributor Author

@yassin-berriai @mateo-berri @Sameerlite could you take a look when you have a moment?

VirtualKeysTable hits an infinite re-render loop because keys?.keys is a fresh reference on every render and gets passed straight into useFilterLogic. This memoizes it before passing it down, which stops the loop.

Small fix for a real perf and usability bug. Would appreciate a merge

…resolve/virtual-keys

# Conflicts:
#	ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ Bytechoreographer
❌ noreply
You have signed the CLA already but the status is still pending? Let us recheck it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants