|
1 | | -import { type ComponentType, useCallback, useEffect, useMemo, useRef } from 'react' |
| 1 | +import { type ComponentType, useCallback, useMemo, useRef, useState } from 'react' |
2 | 2 |
|
3 | 3 | import { Box, Button, Chip, IconButton } from '@mui/material' |
4 | 4 | import { useTheme } from '@mui/material/styles' |
@@ -42,16 +42,33 @@ export function ArrayInput({ |
42 | 42 | [data.inputValues, inputParam.name] |
43 | 43 | ) |
44 | 44 |
|
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). |
46 | 50 | 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++}`) |
53 | 64 | } |
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 | + } |
55 | 72 |
|
56 | 73 | // Use pre-computed itemParameters |
57 | 74 | // Falls back to raw field definitions for nested arrays without show/hide conditions. |
@@ -113,7 +130,7 @@ export function ArrayInput({ |
113 | 130 | const handleDeleteItem = useCallback( |
114 | 131 | (indexToDelete: number) => { |
115 | 132 | const updatedArrayItems = arrayItems.filter((_, i) => i !== indexToDelete) |
116 | | - itemKeysRef.current.splice(indexToDelete, 1) |
| 133 | + setItemKeys((prev) => prev.filter((_, i) => i !== indexToDelete)) |
117 | 134 |
|
118 | 135 | // Notify parent of change (parent will update props, causing re-render) |
119 | 136 | onDataChange?.({ inputParam, newValue: updatedArrayItems }) |
@@ -145,7 +162,7 @@ export function ArrayInput({ |
145 | 162 |
|
146 | 163 | return ( |
147 | 164 | <Box |
148 | | - key={itemKeysRef.current[index]} |
| 165 | + key={effectiveKeys[index]} |
149 | 166 | sx={{ |
150 | 167 | p: 2, |
151 | 168 | mt: 2, |
|
0 commit comments