diff --git a/app/src/components/alert/FormikErrorSnackbar.tsx b/app/src/components/alert/FormikErrorSnackbar.tsx new file mode 100644 index 0000000000..758fddf0a0 --- /dev/null +++ b/app/src/components/alert/FormikErrorSnackbar.tsx @@ -0,0 +1,39 @@ +import Alert from '@mui/material/Alert'; +import Snackbar from '@mui/material/Snackbar'; +import { useFormikContext } from 'formik'; +import { useEffect, useState } from 'react'; + +const FormikErrorSnackbar = () => { + const formikProps = useFormikContext(); + const { errors, submitCount, isSubmitting } = formikProps; + const [openSnackbar, setOpenSnackbar] = useState({ open: false, msg: '' }); + + useEffect(() => { + if (!Object.keys(errors).length || submitCount <= 0 || isSubmitting) { + return; + } + + setOpenSnackbar({ open: true, msg: 'One or more fields are invalid.' }); + }, [errors, submitCount, isSubmitting]); + + const closeSnackBar = () => + setOpenSnackbar((currentState) => { + return { open: false, msg: currentState.msg }; + }); + + return ( + + + {openSnackbar.msg} + + + ); +}; + +export default FormikErrorSnackbar; diff --git a/app/src/components/formik/ScrollToFormikError.tsx b/app/src/components/formik/ScrollToFormikError.tsx deleted file mode 100644 index b20f62b802..0000000000 --- a/app/src/components/formik/ScrollToFormikError.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import Alert from '@mui/material/Alert'; -import Snackbar from '@mui/material/Snackbar'; -import { useFormikContext } from 'formik'; -import { ProjectViewObject } from 'interfaces/useProjectApi.interface'; -import React, { useEffect, useState } from 'react'; - -export interface IScrollToFormikErrorProps { - /** - * An ordered list of field names, which informs which field this component will scroll to when multiple fields are - * in error. - * - * Note: A regex is required if the field name has dynamic components (ie: a field with an array of objects where - * part of the field name is its index in the array field). - * - * @type {((string | RegExp)[])} - * @memberof IScrollToFormikErrorProps - */ - fieldOrder: (string | RegExp)[]; -} - -export const ScrollToFormikError: React.FC = (props) => { - const formikProps = useFormikContext(); - const { errors, submitCount } = formikProps; - const [openSnackbar, setOpenSnackbar] = useState({ open: false, msg: '' }); - - useEffect(() => { - const showSnackBar = (message: string) => { - setOpenSnackbar({ open: true, msg: message }); - }; - - const getAllFieldErrorNames = (obj: object, prefix = '', result: string[] = []) => { - Object.keys(obj).forEach((key) => { - const value = (obj as Record)[key]; - if (!value) return; - - key = Number(key) || key === '0' ? `[${key}]` : key; - - const nextKey = prefix ? `${prefix}.${key}` : key; - - if (typeof value === 'object') { - getAllFieldErrorNames(value, nextKey, result); - } else { - result.push(nextKey); - } - }); - return result; - }; - - const getFirstErrorField = (errorArray: string[]): string | undefined => { - for (const listError of props.fieldOrder) { - for (const trueError of errorArray) { - if (trueError.match(listError) || listError === trueError) { - return trueError; - } - } - } - }; - - const fieldErrorNames = getAllFieldErrorNames(errors); - - const topFieldError = getFirstErrorField(fieldErrorNames); - - if (!topFieldError) { - return; - } - - showSnackBar(`Missing one or more required fields.`); - - const errorElement = document.getElementsByName(topFieldError); - - if (errorElement.length <= 0) { - return; - } - - errorElement[0].scrollIntoView({ behavior: 'smooth', block: 'center' }); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [errors, submitCount]); - - const closeSnackBar = () => - setOpenSnackbar((currentState) => { - return { open: false, msg: currentState.msg }; - }); - - return ( - <> - - - {openSnackbar.msg} - - - - ); -}; diff --git a/app/src/components/species/AncillarySpeciesComponent.tsx b/app/src/components/species/AncillarySpeciesComponent.tsx index 611545befb..3dbc65a12d 100644 --- a/app/src/components/species/AncillarySpeciesComponent.tsx +++ b/app/src/components/species/AncillarySpeciesComponent.tsx @@ -32,6 +32,7 @@ const AncillarySpeciesComponent = () => { required={false} handleAddSpecies={handleAddSpecies} /> + {errors && get(errors, 'species.ancillary_species') && ( { /> )} - ); }; diff --git a/app/src/components/species/FocalSpeciesComponent.tsx b/app/src/components/species/FocalSpeciesComponent.tsx index a4b494106e..d81a3d00eb 100644 --- a/app/src/components/species/FocalSpeciesComponent.tsx +++ b/app/src/components/species/FocalSpeciesComponent.tsx @@ -7,13 +7,11 @@ import SelectedSpecies from './components/SelectedSpecies'; import SpeciesAutocompleteField from './components/SpeciesAutocompleteField'; const FocalSpeciesComponent = () => { - const { values, setFieldValue, setErrors, errors } = useFormikContext(); + const { values, setFieldValue, errors, submitCount } = useFormikContext(); const selectedSpecies: ITaxonomy[] = get(values, 'species.focal_species') || []; - const handleAddSpecies = (species: ITaxonomy) => { setFieldValue(`species.focal_species[${selectedSpecies.length}]`, species); - setErrors([]); }; const handleRemoveSpecies = (species_id: number) => { @@ -32,7 +30,8 @@ const FocalSpeciesComponent = () => { required={true} handleAddSpecies={handleAddSpecies} /> - {errors && get(errors, 'species.focal_species') && ( + + {submitCount > 0 && errors && get(errors, 'species.focal_species') && ( { /> )} - ); }; diff --git a/app/src/features/projects/create/CreateProjectForm.tsx b/app/src/features/projects/create/CreateProjectForm.tsx index 72a24400a8..5322157b4f 100644 --- a/app/src/features/projects/create/CreateProjectForm.tsx +++ b/app/src/features/projects/create/CreateProjectForm.tsx @@ -2,8 +2,8 @@ import { Theme } from '@mui/material'; import Box from '@mui/material/Box'; import Divider from '@mui/material/Divider'; import { makeStyles } from '@mui/styles'; +import FormikErrorSnackbar from 'components/alert/FormikErrorSnackbar'; import HorizontalSplitFormComponent from 'components/fields/HorizontalSplitFormComponent'; -import { ScrollToFormikError } from 'components/formik/ScrollToFormikError'; import { PROJECT_ROLE } from 'constants/roles'; import { Formik, FormikProps } from 'formik'; import { useAuthStateContext } from 'hooks/useAuthStateContext'; @@ -101,13 +101,12 @@ const CreateProjectForm: React.FC = (props) => { innerRef={formikRef} initialValues={props.initialValues || initialProjectFieldData} validationSchema={validationProjectYupSchema} - validateOnBlur={true} + validateOnBlur={false} validateOnChange={false} enableReinitialize={true} onSubmit={handleSubmit}> <> - - + = (props) => { innerRef={formikRef} initialValues={initialProjectFieldData as unknown as IUpdateProjectRequest} validationSchema={validationProjectYupSchema} - validateOnBlur={true} + validateOnBlur={false} validateOnChange={false} enableReinitialize={true} onSubmit={handleSubmit}> <> + { innerRef={formikRef} initialValues={surveyInitialValues} validationSchema={surveyYupSchemas} - validateOnBlur={true} + validateOnBlur={false} validateOnChange={false} onSubmit={handleSubmit}> <> - + }> = (props) => { innerRef={props.formikRef} initialValues={surveyInitialValues} validationSchema={surveyEditYupSchemas} - validateOnBlur={true} + validateOnBlur={false} validateOnChange={false} onSubmit={props.handleSubmit}> <> - - +