Hey,
Thanks for the library, very cool stuff. I was trying to integrated it into my app (and testing the web version). Currently using 3.0.0-beta.43
Claude helped me debug this issue and with the 1 liner it doesn't give me those logs anymore.
In React 18 concurrent mode, the LegendList component can throw the "Detected overlapping key" error even when all keys are unique. This is a false positive caused by a stale idCache across interrupted render attempts.
Root Cause
React 18 concurrent mode can render a component multiple times before committing (e.g., when props change mid-render). On the first render attempt, the isFirstLocal path calls updateItemPositions(ctx, true) directly, which populates idCache with the current data's keys.
If React interrupts that render and starts a new one with updated data (e.g., items reordered), the second render attempt also has isFirstLocal = true because the useLayoutEffect that sets state.isFirst = false never committed. The direct updateItemPositions(ctx, true) call is made again — but unlike the calculateItemsInView path, it does not clear idCache first.
The result: idCache has stale entries from the first (abandoned) render, the loop reads them instead of calling getId, and items that have moved positions appear to have duplicate keys.
// calculateItemsInView path (safe):
if (dataChanged) {
idCache.length = 0; // ✅ clears cache
...
}
updateItemPositions(ctx, dataChanged, ...);
// isFirstLocal path (buggy):
if (isFirstLocal) {
initializeStateVars(false);
updateItemPositions(ctx, /*dataChanged*/ true); // ❌ idCache not cleared
}
Reproduction
- Use React 18 with concurrent mode
- Render a
LegendList with data that changes quickly (e.g., items reordering) during initial mount
- The "Detected overlapping key" error fires in dev mode even though
keyExtractor returns unique values for all items
Fix
Clear idCache inside updateItemPositions itself when dataChanged=true, so the behavior is correct regardless of call path:
// Inside updateItemPositions, before the loop:
if (dataChanged && idCache.length > 0) {
idCache.length = 0;
}
This is a no-op when called from calculateItemsInView (which already clears it), and fixes the bug when called from the isFirstLocal path.
Hey,
Thanks for the library, very cool stuff. I was trying to integrated it into my app (and testing the web version). Currently using 3.0.0-beta.43
Claude helped me debug this issue and with the 1 liner it doesn't give me those logs anymore.
In React 18 concurrent mode, the
LegendListcomponent can throw the "Detected overlapping key" error even when all keys are unique. This is a false positive caused by a staleidCacheacross interrupted render attempts.Root Cause
React 18 concurrent mode can render a component multiple times before committing (e.g., when props change mid-render). On the first render attempt, the
isFirstLocalpath callsupdateItemPositions(ctx, true)directly, which populatesidCachewith the current data's keys.If React interrupts that render and starts a new one with updated data (e.g., items reordered), the second render attempt also has
isFirstLocal = truebecause theuseLayoutEffectthat setsstate.isFirst = falsenever committed. The directupdateItemPositions(ctx, true)call is made again — but unlike thecalculateItemsInViewpath, it does not clearidCachefirst.The result:
idCachehas stale entries from the first (abandoned) render, the loop reads them instead of callinggetId, and items that have moved positions appear to have duplicate keys.Reproduction
LegendListwith data that changes quickly (e.g., items reordering) during initial mountkeyExtractorreturns unique values for all itemsFix
Clear
idCacheinsideupdateItemPositionsitself whendataChanged=true, so the behavior is correct regardless of call path:This is a no-op when called from
calculateItemsInView(which already clears it), and fixes the bug when called from theisFirstLocalpath.