From e39b9ff037cad659a109d5215cf9513f3ecad7ec Mon Sep 17 00:00:00 2001 From: j-sanaa Date: Tue, 17 Mar 2026 13:12:19 -0700 Subject: [PATCH] fix (AgentFlow) - generate ArrayInput item keys synchronously to prevent missing React keys (#6001) * Fix to remove useEffect * Fix gemini comment --- packages/agentflow/src/atoms/ArrayInput.tsx | 39 +++++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/agentflow/src/atoms/ArrayInput.tsx b/packages/agentflow/src/atoms/ArrayInput.tsx index b1e468582..f719f7fbf 100644 --- a/packages/agentflow/src/atoms/ArrayInput.tsx +++ b/packages/agentflow/src/atoms/ArrayInput.tsx @@ -1,4 +1,4 @@ -import { type ComponentType, useCallback, useEffect, useMemo, useRef } from 'react' +import { type ComponentType, useCallback, useMemo, useRef, useState } from 'react' import { Box, Button, Chip, IconButton } from '@mui/material' import { useTheme } from '@mui/material/styles' @@ -42,16 +42,33 @@ export function ArrayInput({ [data.inputValues, inputParam.name] ) - // Stable keys for array items — avoids using index as React key + // Stable keys for array items — avoids using index as React key. + // Keys are held in state so they persist across renders. A local variable + // (`effectiveKeys`) is used for the current render pass so that newly + // generated keys are available immediately (setState alone would only + // take effect on the *next* render, leaving keys undefined in this one). const idCounterRef = useRef(0) - const itemKeysRef = useRef([]) - - // Grow keys array when new items appear (e.g. on mount or external data changes) - useEffect(() => { - while (itemKeysRef.current.length < arrayItems.length) { - itemKeysRef.current.push(`item-${idCounterRef.current++}`) + const [itemKeys, setItemKeys] = useState(() => { + const initial: string[] = [] + while (initial.length < arrayItems.length) { + initial.push(`item-${idCounterRef.current++}`) } - }, [arrayItems.length]) + return initial + }) + + let effectiveKeys = itemKeys + if (effectiveKeys.length < arrayItems.length) { + const nextKeys = [...effectiveKeys] + while (nextKeys.length < arrayItems.length) { + nextKeys.push(`item-${idCounterRef.current++}`) + } + setItemKeys(nextKeys) + effectiveKeys = nextKeys + } else if (effectiveKeys.length > arrayItems.length) { + const trimmed = effectiveKeys.slice(0, arrayItems.length) + setItemKeys(trimmed) + effectiveKeys = trimmed + } // Use pre-computed itemParameters // Falls back to raw field definitions for nested arrays without show/hide conditions. @@ -113,7 +130,7 @@ export function ArrayInput({ const handleDeleteItem = useCallback( (indexToDelete: number) => { const updatedArrayItems = arrayItems.filter((_, i) => i !== indexToDelete) - itemKeysRef.current.splice(indexToDelete, 1) + setItemKeys((prev) => prev.filter((_, i) => i !== indexToDelete)) // Notify parent of change (parent will update props, causing re-render) onDataChange?.({ inputParam, newValue: updatedArrayItems }) @@ -145,7 +162,7 @@ export function ArrayInput({ return (