Skip to content

Commit e39b9ff

Browse files
authored
fix (AgentFlow) - generate ArrayInput item keys synchronously to prevent missing React keys (#6001)
* Fix to remove useEffect * Fix gemini comment
1 parent b2afa10 commit e39b9ff

1 file changed

Lines changed: 28 additions & 11 deletions

File tree

packages/agentflow/src/atoms/ArrayInput.tsx

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type ComponentType, useCallback, useEffect, useMemo, useRef } from 'react'
1+
import { type ComponentType, useCallback, useMemo, useRef, useState } from 'react'
22

33
import { Box, Button, Chip, IconButton } from '@mui/material'
44
import { useTheme } from '@mui/material/styles'
@@ -42,16 +42,33 @@ export function ArrayInput({
4242
[data.inputValues, inputParam.name]
4343
)
4444

45-
// Stable keys for array items — avoids using index as React key
45+
// Stable keys for array items — avoids using index as React key.
46+
// Keys are held in state so they persist across renders. A local variable
47+
// (`effectiveKeys`) is used for the current render pass so that newly
48+
// generated keys are available immediately (setState alone would only
49+
// take effect on the *next* render, leaving keys undefined in this one).
4650
const idCounterRef = useRef(0)
47-
const itemKeysRef = useRef<string[]>([])
48-
49-
// Grow keys array when new items appear (e.g. on mount or external data changes)
50-
useEffect(() => {
51-
while (itemKeysRef.current.length < arrayItems.length) {
52-
itemKeysRef.current.push(`item-${idCounterRef.current++}`)
51+
const [itemKeys, setItemKeys] = useState<string[]>(() => {
52+
const initial: string[] = []
53+
while (initial.length < arrayItems.length) {
54+
initial.push(`item-${idCounterRef.current++}`)
55+
}
56+
return initial
57+
})
58+
59+
let effectiveKeys = itemKeys
60+
if (effectiveKeys.length < arrayItems.length) {
61+
const nextKeys = [...effectiveKeys]
62+
while (nextKeys.length < arrayItems.length) {
63+
nextKeys.push(`item-${idCounterRef.current++}`)
5364
}
54-
}, [arrayItems.length])
65+
setItemKeys(nextKeys)
66+
effectiveKeys = nextKeys
67+
} else if (effectiveKeys.length > arrayItems.length) {
68+
const trimmed = effectiveKeys.slice(0, arrayItems.length)
69+
setItemKeys(trimmed)
70+
effectiveKeys = trimmed
71+
}
5572

5673
// Use pre-computed itemParameters
5774
// Falls back to raw field definitions for nested arrays without show/hide conditions.
@@ -113,7 +130,7 @@ export function ArrayInput({
113130
const handleDeleteItem = useCallback(
114131
(indexToDelete: number) => {
115132
const updatedArrayItems = arrayItems.filter((_, i) => i !== indexToDelete)
116-
itemKeysRef.current.splice(indexToDelete, 1)
133+
setItemKeys((prev) => prev.filter((_, i) => i !== indexToDelete))
117134

118135
// Notify parent of change (parent will update props, causing re-render)
119136
onDataChange?.({ inputParam, newValue: updatedArrayItems })
@@ -145,7 +162,7 @@ export function ArrayInput({
145162

146163
return (
147164
<Box
148-
key={itemKeysRef.current[index]}
165+
key={effectiveKeys[index]}
149166
sx={{
150167
p: 2,
151168
mt: 2,

0 commit comments

Comments
 (0)