Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/enhanced-select-input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,27 @@ export function useEnhancedSelectInput<V>({
// 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<string>()
const duplicates = new Set<string>()
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) {
Expand Down
51 changes: 51 additions & 0 deletions src/test/enhanced-select-input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<EnhancedSelectInput
items={[
{ label: 'A', value: { id: 1 } },
{ label: 'B', value: { id: 2 } },
]}
/>
)

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(
<EnhancedSelectInput
items={[
{ key: 'item-1', label: 'A', value: { id: 1 } },
{ key: 'item-2', label: 'B', value: { id: 2 } },
]}
/>
)

await delay()
t.false(warnings.some((w) => w.includes('[ink-enhanced-select-input]')))
} finally {
console.warn = originalWarn
}
})
Loading