diff --git a/src/enhanced-select-input/index.tsx b/src/enhanced-select-input/index.tsx index 4bdce83..026a2cb 100644 --- a/src/enhanced-select-input/index.tsx +++ b/src/enhanced-select-input/index.tsx @@ -156,7 +156,27 @@ export function useEnhancedSelectInput({ // If the item at that position is still enabled we keep it; otherwise we // resolve the nearest valid index from the same position, so the selection // stays as close as possible to where the user left off. + // Also warn in development when duplicate React keys are detected — + // this happens when V is an object and item.key is not set, causing + // String(value) to produce "[object Object]" for every item. useEffect(() => { + if (process.env['NODE_ENV'] !== 'production' && items.length > 0) { + const keys = items.map((item) => item.key ?? String(item.value)) + const seen = new Set() + const duplicates = new Set() + for (const k of keys) { + if (seen.has(k)) duplicates.add(k) + else seen.add(k) + } + + if (duplicates.size > 0) { + console.warn( + `[ink-enhanced-select-input] Duplicate item keys detected: ${[...duplicates].join(', ')}. ` + + 'Set a unique "key" on each item — this is required when value is a non-primitive type (e.g. object).' + ) + } + } + if (items.length === 0) return const currentItem = items[selectedIndex] if (!currentItem || currentItem.disabled) { diff --git a/src/test/enhanced-select-input.test.tsx b/src/test/enhanced-select-input.test.tsx index 4e4af12..ea610d6 100644 --- a/src/test/enhanced-select-input.test.tsx +++ b/src/test/enhanced-select-input.test.tsx @@ -1468,3 +1468,54 @@ test('selection preserved when items update but current slot is still valid', as await delay() t.is(highlighted, 'B') }) + +// --- #16: duplicate key warning --- + +test('warns in development when object-valued items have no key field', async (t) => { + const warnings: string[] = [] + const originalWarn = console.warn + console.warn = (...args: unknown[]) => { + warnings.push(String(args[0])) + } + + try { + render( + + ) + + await delay() + t.true(warnings.some((w) => w.includes('[ink-enhanced-select-input]'))) + t.true(warnings.some((w) => w.includes('Duplicate item keys'))) + } finally { + console.warn = originalWarn + } +}) + +test('no duplicate key warning when all items have explicit keys', async (t) => { + const warnings: string[] = [] + const originalWarn = console.warn + console.warn = (...args: unknown[]) => { + warnings.push(String(args[0])) + } + + try { + render( + + ) + + await delay() + t.false(warnings.some((w) => w.includes('[ink-enhanced-select-input]'))) + } finally { + console.warn = originalWarn + } +})