diff --git a/airbyte-webapp/src/views/Connector/ConnectorForm/ConnectorForm.test.tsx b/airbyte-webapp/src/views/Connector/ConnectorForm/ConnectorForm.test.tsx index 27a9e27a60b2..c39270c4b493 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorForm/ConnectorForm.test.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorForm/ConnectorForm.test.tsx @@ -53,7 +53,7 @@ const useAddPriceListItem = (container: HTMLElement) => { const arrayOfObjectsEditModal = getByTestId(document.body, "arrayOfObjects-editModal"); const getPriceListInput = (index: number, key: string) => - arrayOfObjectsEditModal.querySelector(`input[name='__temp__connectionConfiguration_priceList${index}.${key}']`); + arrayOfObjectsEditModal.querySelector(`input[name='connectionConfiguration.priceList\\[${index}\\].${key}']`); // Type items into input const nameInput = getPriceListInput(index, "name"); diff --git a/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/ArraySection.tsx b/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/ArraySection.tsx index 655adbbce883..b3e27ad3c8f5 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/ArraySection.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/ArraySection.tsx @@ -42,15 +42,27 @@ const getItemDescription = (item: Record, properties: FormBlock[ }; export const ArraySection: React.FC = ({ formField, path, disabled }) => { - const [field, , fieldHelper] = useField(path); - const [editIndex, setEditIndex] = useState(); + const [field, , fieldHelper] = useField>>(path); + const [editIndex, setEditIndex] = useState(); + // keep the previous state of the currently edited item around so it can be restored on cancelling the form + const [originalItem, setOriginalItem] = useState | undefined>(); - const items = useMemo(() => field.value ?? [], [field.value]); + const items: Array> = useMemo(() => field.value ?? [], [field.value]); + + // keep the list of rendered items stable as long as editing is in progress + const itemsWithOverride = useMemo(() => { + if (typeof editIndex === "undefined") { + return items; + } + return items.map((item, index) => (index === editIndex ? originalItem : item)).filter(Boolean) as Array< + Record + >; + }, [editIndex, originalItem, items]); const { renderItemName, renderItemDescription } = useMemo(() => { const { properties } = formField.properties as FormGroupItem; - const details = items.map((item: Record) => { + const details = itemsWithOverride.map((item: Record) => { const name = getItemName(item, properties); const description = getItemDescription(item, properties); return { @@ -63,10 +75,23 @@ export const ArraySection: React.FC = ({ formField, path, dis renderItemName: (_: unknown, index: number) => details[index].name, renderItemDescription: (_: unknown, index: number) => details[index].description, }; - }, [items, formField.properties]); + }, [itemsWithOverride, formField.properties]); const clearEditIndex = () => setEditIndex(undefined); + // on cancelling editing, either remove the item if it has been a new one or put back the old value in the form + const onCancel = () => { + const newList = [...field.value]; + if (!originalItem) { + newList.pop(); + } else if (editIndex !== undefined && originalItem) { + newList.splice(editIndex, 1, originalItem); + } + + fieldHelper.setValue(newList); + clearEditIndex(); + }; + return ( = ({ formField, path, dis render={(arrayHelpers) => ( { + setEditIndex(n); + setOriginalItem(items[n]); + }} onRemove={arrayHelpers.remove} - onCancel={clearEditIndex} - items={items} + onCancel={onCancel} + items={itemsWithOverride} renderItemName={renderItemName} renderItemDescription={renderItemDescription} disabled={disabled} @@ -93,16 +121,8 @@ export const ArraySection: React.FC = ({ formField, path, dis path={`${path}[${editIndex ?? 0}]`} disabled={disabled} item={item} - onDone={(updatedItem) => { - const updatedValue = - editIndex !== undefined && editIndex < items.length - ? items.map((item: unknown, index: number) => (index === editIndex ? updatedItem : item)) - : [...items, updatedItem]; - - fieldHelper.setValue(updatedValue); - clearEditIndex(); - }} - onCancel={clearEditIndex} + onDone={clearEditIndex} + onCancel={onCancel} /> )} /> diff --git a/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/VariableInputFieldForm.tsx b/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/VariableInputFieldForm.tsx index a984d31d51ac..fa208c9d6aca 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/VariableInputFieldForm.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorForm/components/Sections/VariableInputFieldForm.tsx @@ -1,5 +1,4 @@ import { useField } from "formik"; -import { useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useAsync, useEffectOnce } from "react-use"; import * as yup from "yup"; @@ -29,16 +28,10 @@ export const VariableInputFieldForm: React.FC = ({ onDone, onCancel, }) => { - // This form creates a temporary field for Formik to prevent the field from rendering in - // the service form while it's being created or edited since it reuses the FormSection component. - // The temp field is cleared when this form is done or canceled. - const variableInputFieldPath = useMemo(() => `__temp__${path.replace(/\./g, "_").replace(/\[|\]/g, "")}`, [path]); - const [field, , fieldHelper] = useField(variableInputFieldPath); + const [field, , fieldHelper] = useField(path); const { validationSchema } = useConnectorForm(); // Copy the validation from the original field to ensure that the form has all the required values field out correctly. - // One side effect of this is that validation errors will not be shown in this form because the validationSchema does not - // contain info about the temp field. const { value: isValid } = useAsync( async (): Promise => yup.reach(validationSchema, path).isValid(field.value), [field.value, path, validationSchema] @@ -63,7 +56,7 @@ export const VariableInputFieldForm: React.FC = ({ return ( <> - +