diff --git a/src/frontend/src/components/forms/fields/TextField.tsx b/src/frontend/src/components/forms/fields/TextField.tsx index 8e95011b4b..09fd716228 100644 --- a/src/frontend/src/components/forms/fields/TextField.tsx +++ b/src/frontend/src/components/forms/fields/TextField.tsx @@ -1,6 +1,5 @@ import { t } from '@lingui/core/macro'; import { TextInput, Tooltip } from '@mantine/core'; -import { useDebouncedValue } from '@mantine/hooks'; import { IconCopyCheck, IconX } from '@tabler/icons-react'; import { type ReactNode, @@ -40,30 +39,26 @@ export default function TextField({ const { value } = useMemo(() => field, [field]); - const [rawText, setRawText] = useState(value || ''); + const [textValue, setTextValue] = useState(value || ''); - const [debouncedText] = useDebouncedValue(rawText, 100); + const onTextChange = useCallback( + (value: any) => { + setTextValue(value); + onChange(value); + }, + [onChange] + ); useEffect(() => { - setRawText(value || ''); + setTextValue(value || ''); }, [value]); - const onTextChange = useCallback((value: any) => { - setRawText(value); - }, []); - - useEffect(() => { - if (debouncedText !== value) { - onChange(debouncedText); - } - }, [debouncedText]); - // Construct a "right section" for the text field const textFieldRightSection: ReactNode = useMemo(() => { if (definition.rightSection) { // Use the specified override value return definition.rightSection; - } else if (value) { + } else if (textValue) { if (!definition.required && !definition.disabled) { // Render a button to clear the text field return ( @@ -78,7 +73,7 @@ export default function TextField({ ); } } else if ( - !value && + !textValue && definition.placeholder && placeholderAutofill && !definition.disabled @@ -94,7 +89,7 @@ export default function TextField({ ); } - }, [placeholderAutofill, definition, value]); + }, [placeholderAutofill, definition, textValue]); return ( onTextChange(event.currentTarget.value)} onBlur={(event) => { - if (event.currentTarget.value != value) { - onChange(event.currentTarget.value); + if (event.currentTarget.value != textValue) { + onTextChange(event.currentTarget.value); } }} onKeyDown={(event) => { if (event.code === 'Enter') { // Bypass debounce on enter key - onChange(event.currentTarget.value); + onTextChange(event.currentTarget.value); } onKeyDown(event.code); }} diff --git a/src/frontend/src/components/importer/ImporterColumnSelector.tsx b/src/frontend/src/components/importer/ImporterColumnSelector.tsx index 9ad87056d9..4036b4f3b5 100644 --- a/src/frontend/src/components/importer/ImporterColumnSelector.tsx +++ b/src/frontend/src/components/importer/ImporterColumnSelector.tsx @@ -16,6 +16,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; import { apiUrl } from '@lib/functions/Api'; import type { ApiFormFieldType } from '@lib/types/Forms'; +import { useDebouncedValue } from '@mantine/hooks'; import { useApi } from '../../contexts/ApiContext'; import type { ImportSessionState } from '../../hooks/UseImportSession'; import { StandaloneField } from '../forms/StandaloneField'; @@ -83,6 +84,14 @@ function ImporterDefaultField({ }) { const api = useApi(); + const [rawValue, setRawValue] = useState(''); + + const fieldType: string = useMemo(() => { + return session.availableFields[fieldName]?.type; + }, [fieldName, session.availableFields]); + + const [value] = useDebouncedValue(rawValue, fieldType == 'string' ? 500 : 10); + const onChange = useCallback( (value: any) => { // Update the default value for the field @@ -105,6 +114,11 @@ function ImporterDefaultField({ [fieldName, session, session.fieldDefaults] ); + // Update the default value after the debounced value changes + useEffect(() => { + onChange(value); + }, [value]); + const fieldDef: ApiFormFieldType = useMemo(() => { let def: any = session.availableFields[fieldName]; @@ -114,7 +128,10 @@ function ImporterDefaultField({ value: session.fieldDefaults[fieldName], field_type: def.type, description: def.help_text, - onValueChange: onChange + required: false, + onValueChange: (value: string) => { + setRawValue(value); + } }; } diff --git a/src/frontend/tests/pui_forms.spec.ts b/src/frontend/tests/pui_forms.spec.ts index a04d7a0e5d..4de55fc9b7 100644 --- a/src/frontend/tests/pui_forms.spec.ts +++ b/src/frontend/tests/pui_forms.spec.ts @@ -98,7 +98,7 @@ test('Forms - Supplier Validation', async ({ browser }) => { // Check for validation errors await page.getByText('Form Error').waitFor(); await page.getByText('Errors exist for one or more').waitFor(); - await page.getByText('This field may not be blank.').waitFor(); + await page.getByText('This field is required').waitFor(); await page.getByText('Enter a valid URL.').waitFor(); // Fill out another field, expect that the errors persist @@ -106,7 +106,7 @@ test('Forms - Supplier Validation', async ({ browser }) => { .getByLabel('text-field-description', { exact: true }) .fill('A description'); await page.waitForTimeout(250); - await page.getByText('This field may not be blank.').waitFor(); + await page.getByText('This field is required').waitFor(); await page.getByText('Enter a valid URL.').waitFor(); // Generate a unique supplier name diff --git a/src/frontend/tests/pui_importing.spec.ts b/src/frontend/tests/pui_importing.spec.ts index 868a3b857c..7d1260840e 100644 --- a/src/frontend/tests/pui_importing.spec.ts +++ b/src/frontend/tests/pui_importing.spec.ts @@ -111,9 +111,15 @@ test('Importing - BOM', async ({ browser }) => { // Delete selected rows await page - .getByRole('dialog', { name: 'Importing Data Upload File 2' }) + .getByRole('dialog', { name: 'Importing Data Upload File' }) + .getByLabel('action-button-delete-selected') + .waitFor(); + await page.waitForTimeout(200); + await page + .getByRole('dialog', { name: 'Importing Data Upload File' }) .getByLabel('action-button-delete-selected') .click(); + await page.getByRole('button', { name: 'Delete', exact: true }).click(); await page.getByText('Success', { exact: true }).waitFor();