I'm using react for the first time. I have hooked up a form with Formik and have all the validation stuff working but I am stuck on keeping form values if there was a problem.
The form collects data to add a record to the database. If the API response has data, the insert was successful and I show a success Alert. If the insert failed, data is empty because null was returned from the API and I show a danger Alert.
My question is how do I keep the form fields' values if there was an error? I want to close the danger Alert and still have the form fields populated so the user does not have to start over. I don't need to do this on success because the information the user entered got into the database.
I feel like this has something to do with the state of the form itself or the fields but I can't figure how to retain the values. I tried doing ...values in onSubmit but that didn't work (don't quite understand that spread thing). Any help would be appreciated.
This is what I have:
import React, { useState } from "react"
import * as Yup from "yup"
import { Formik, Form, Field } from "formik"
import "../../custom.css"
import * as apiService from "./../../services/apiService"
import DatePicker from "../../utils/DatePicker"
import "react-datepicker/dist/react-datepicker.css"
import Alert from "reactstrap/lib/Alert"
import { Button } from "reactstrap"
import "bootstrap/dist/css/bootstrap.min.css"
const CompanyMasterAdd = () => {
const [showAlert, setShowAlert] = useState(false)
const [apiResponse, setApiResponse] = useState(null)
const urlRegex =
/((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=\\+\\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\\+\\$,\w]+@)[A-Za-z0-9.-]+)((?:\/[\\+~%\\/.\w-_]*)?\??(?:[-\\+=&;%@.\w_]*)#?(?:[\w]*))?)/
//Setup values for drop downs so we can validate against selection
//This one is set here because we need to access statuses in the YUP validation
const statuses = ["1", "2", "3", "4", "5"]
const statusOptions = statuses.map((statusID, key) => (
<option value={statusID} key={key}>
{statusID}
</option>
))
//setup validation requirements and error messages for fields via Yup
const companyMasterAddSchema = Yup.object().shape({
companyId: Yup.number()
//custom required message when required field not filled in
.required("Company Id is required")
//if it's not a number, display custom error message
.typeError("Company Id must be a number")
.positive()
.integer(),
companyName: Yup.string()
.max(75, "Must be 75 characters or less")
.required("Company Name is required"),
databaseName: Yup.string()
.required("Database Name is required")
.max(100, "Must be 100 characters or less"),
statusID: Yup.string().required("Status ID is required").oneOf(statuses),
website: Yup.string().matches(urlRegex, "Website URL is not valid"),
})
//Setup values for drop downs so we can validate against selection
const companyTypes = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
const companyTypeOptions = companyTypes.map((companyTypeID, key) => (
<option value={companyTypeID} key={key}>
{companyTypeID}
</option>
))
//dismiss alert when button is closed
const onDismiss = () => {
setShowAlert(false)
}
return (
<div id="companyMasterPageContainer">
<h1>Add Company Master record</h1>
<hr />
{showAlert ? (
<div>
<Alert
color={apiResponse && !apiResponse.data ? "danger" : "success"}
>
<h4 className="alert-heading">
{apiResponse && !apiResponse.data ? "ERROR" : "SUCCESS"}
</h4>
<p>
{apiResponse && !apiResponse.data
?
//TODO: keep form data populated if there is a failure
//TODO: get the actual error from the API somehow
"An error occurred. Check the ProcedureErrorLog table"
: "Record added"}
</p>
<hr />
<Button onClick={() => onDismiss()}>Dismiss</Button>
</Alert>
</div>
) : (
// show form if showAlert is false
<div id="companyMasterAddFormContainer">
<h2>Form area</h2>
<Formik
initialValues={{
companyId: "",
companyName: "",
website: "",
fiscalYearEnd: null,
taxIDNumber: "",
companyTypeID: "",
isKeyCompany: false,
databaseName: "",
statusID: "",
}}
validationSchema={companyMasterAddSchema}
onSubmit={async (
values,
{ resetForm, setSubmitting, isSubmitting }
) => {
apiService.addCompanyMaster(values).then((response) => {
setApiResponse(response)
setShowAlert(true)
})
}}
>
{({ errors, touched, values, setFieldValue }) => (
<Form id="companyMasterAddForm">
<div>
<span className="requiredStar">
* indicates a required field
</span>
</div>
<div className="row">
<div className="formLabel">
<label
htmlFor="companyId"
className="companyMasterAddFormLabel"
>
Company ID <span className="requiredStar">*</span>
</label>
</div>
<div className="formInput">
<Field
value={values.companyId}
type="text"
id="companyId"
className="companyMasterAddFormField"
name="companyId"
placeholder="e.g., 1,2,3..."
/>
{/* If this field has been touched, and it contains an error, display it */}
{touched.companyId && errors.companyId && (
<div className="formError">{errors.companyId}</div>
)}
</div>
</div>
<div className="row">
<div className="formLabel">
<label
className="companyMasterAddFormLabel"
htmlFor="fiscalYearEnd"
>
Fiscal Year End
</label>
</div>
<div className="formInput">
<DatePicker
name="fiscalYearEnd"
value={values.fiscalYearEnd}
onChange={setFieldValue}
className="companyMasterAddFormField"
class="companyMasterAddFormField"
/>
{/* If this field has been touched, and it contains an error, display it */}
{touched.fiscalYearEnd && errors.fiscalYearEnd && (
<div className="formError">{errors.fiscalYearEnd}</div>
)}
</div>
</div>
<div className="row">
<button
type="submit"
style={{
textAlign: "center",
marginLeft: "30%",
marginRight: "30%",
}}
>
Submit
</button>
</div>
</Form>
)}
</Formik>
</div>
)}
</div>
)
}
export default CompanyMasterAdd
UPDATE - GOT IT WORKING
Separated the conditional logic for the alert and the form:
{
showAlert && (
<div>
<Alert
color={apiResponse && !apiResponse.data ? "danger" : "success"}
>
<h4 className="alert-heading">
{apiResponse && !apiResponse.data ? "ERROR" : "SUCCESS"}
</h4>
<p>
{apiResponse && !apiResponse.data
? //TODO: keep form data populated if there is a failure
"An error occurred. Check the xyztable in the database"
: "Record added"}
</p>
<hr />
<Button onClick={() => onDismiss()}>Dismiss</Button>
</Alert>
</div>
)
// show form if showAlert is false
}
<div
id="companyMasterAddFormContainer"
style={{ visibility: open ? "visible" : "hidden" }}
>
<h2>Form area</h2>
<Formik
initialValues={{
BONUS - HIDE FORM WHILE ALERT IS BEING DISPLAYED
- Added new state variable:
const [open, setOpen] = useState(true)
- Added visibility style in the div container for the form:
<div
id="companyMasterAddFormContainer"
style={{ visibility: open ? "visible" : "hidden" }}
>
- Toggle the state of open in the onSubmit area of form:
onSubmit={async (
values,
{ resetForm, setSubmitting, isSubmitting }
) => {
setSubmitting(false)
apiService.addCompanyMaster(values).then((response) => {
setApiResponse(response)
setShowAlert(true)
setOpen(false)
if (response && response.data) {
resetForm()
}
})
- Toggle the state on the alert dismissal:
const onDismiss = () => {
setShowAlert(false)
setOpen(true)
}