I am trying to validate a form. When the page is refreshed and I click on submit button then only the last element's error is generated, it should generate error for every input according to validation.
Screenshot of form issue
Form validation is working perfect with onChange event. But it doesn't work fine when page is refreshed and I click on submit button without putting values to inputs.
When I click on "Submit" button, It generates an object {message: 'Required'}, In this object, only message key is generated. It should generate object with all input names like {name: 'Required', email: 'Required', message: 'Required'}.
Here is my code:
import { useState } from "react";
function ContactForm() {
const [fields, setFields] = useState({});
const [errors, setErrors] = useState({});
const [formValid, setFormValid] = useState(false);
async function handleOnSubmit(e) {
e.preventDefault();
console.log('fields', fields);
console.log('errors', errors);
console.log('formValid', formValid);
const formData = {};
[...e.currentTarget.elements].map((field) => {
if (!field.name) return false;
checkValidation([field.name], field.value);
setFields({...fields, [field.name]: field.value});
});
if (formValid === false) return false;
try {
const response = await fetch('/api/mail', {
method: 'post',
body: JSON.stringify(formData)
});
const body = await response.json();
console.log(body);
} catch (error) {
console.log(error);
}
console.log(formData);
}
function handleValidation(e) {
const name = e.target.name;
const value = e.target.value;
checkValidation(name, value);
}
function checkValidation(name, value) {
if (value === "") {
delete fields[name];
setErrors({...errors, [name]: 'Required'});
setFormValid(false);
return false;
}
delete errors[name];
setFields({...fields, [name]: value});
setFormValid(true);
// Special validation for email
emailValidation(name, value);
console.log('fields on validaton', fields);
console.log('errors on validation', errors);
}
// Email Validation
function emailValidation(name, value) {
const emailRegex = new RegExp(/[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,15}/g);
if ((name == 'email') && (emailRegex.test(value) === false)) {
delete fields[name];
setErrors({...errors, [name]: 'Email is not validate'});
setFormValid(false);
}
}
return (
<div className="mt-5 mt-md-0">
<h1 className="section-title h1-responsive text-center mb-4">
<span>
Contact Us
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 456.99 38"><defs><style dangerouslySetInnerHTML={{__html: ".cls-1{fill:#cb0a34;}" }} /></defs><title>latest from out videos</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path className="cls-1" d="M456.28,8.29a4.6,4.6,0,0,0,.29-.59c0-.11.08-.21.11-.31a4.56,4.56,0,0,0,.21-.72l0-.21A6.11,6.11,0,0,0,457,5.6h0a5.5,5.5,0,1,0-9.24,4C422.6,9.89,288,9.52,281.13,9.52h-31c-7.86,0-15.13,2.18-21.14,7.26a30.14,30.14,0,0,0-8.72-5.25c-4.68-1.82-9.42-2-14.32-2H9.24A5.5,5.5,0,1,0,0,5.5v0c0,.05,0,.09,0,.14a3.4,3.4,0,0,0,0,.45c0,.12,0,.23,0,.35l.06.3A1.82,1.82,0,0,0,.22,7,5.48,5.48,0,0,0,4.5,10.9a5.13,5.13,0,0,0,1.12.16c.85,0,1.7,0,2.54,0H209.44a28.85,28.85,0,0,1,19,7.26,28.31,28.31,0,0,0-6.38,9.13L229,38.29,236,27.45h0a28.11,28.11,0,0,0-6.38-9.12A28.8,28.8,0,0,1,241.09,12c4-1.09,8-1,12.14-1H451l.54,0a5.47,5.47,0,0,0,3.21-1l.07,0s0,0,.05-.05a4.53,4.53,0,0,0,.62-.54l.24-.26a6.41,6.41,0,0,0,.39-.52C456.15,8.52,456.21,8.4,456.28,8.29Z" /></g></g></svg>
</span>
</h1>
<form className="contact_form" method="post" onSubmit={handleOnSubmit}>
<div className="response-status"></div>
<div className="form-group row">
<div className="col-md-6 mb-2">
<label className="mb-1">Name <span className="text-danger">*</span></label>
<input
type="text"
name="name"
className={`form-control ${errors.name ? 'is-invalid' : ''}`}
onChange={handleValidation}
/>
{errors.name && <span className="text-danger">{errors.name}</span>}
</div>
<div className="col-md-6 mb-2">
<label className="mb-1">Email <span className="text-danger">*</span></label>
<input
type="text"
name="email"
className={`form-control ${errors.email ? 'is-invalid' : ''}`}
onChange={handleValidation}
/>
{errors.email && <span className="text-danger">{errors.email}</span>}
</div>
<div className="col-md-12 mb-2">
<label className="mb-1">Subject:</label>
<input type="text" name="subject" className="form-control" />
</div>
<div className="col-md-12 mb-3">
<label className="mb-1">Message <span className="text-danger">*</span></label>
<textarea
name="message"
className={`form-control ${errors.message ? 'is-invalid' : ''}`}
rows="5"
onChange={handleValidation}
></textarea>
{errors.message && <span className="text-danger">{errors.message}</span>}
</div>
<div className="col-md-12">
<button type="submit" className="btn btn-primary btn-block rounded waves-effect waves-light">Send Message</button>
</div>
</div>
</form>
</div>
)
}
export default ContactForm
I don't know why it is not working fine. Please check the code. I can't find the issue in my code, I am stuck.
You are setting states from so many places and inside a loop too. At some point the value might be stale or unreliable.
If you want to update a state's value based on previous value it is recommended to use this pattern :
setState(prevState => prevState*2)
Basically use previous state's value to get the new state using this callback pattern.
import { useState } from "react";
import "./styles.css";
export default function App() {
const [fields, setFields] = useState({});
const [errors, setErrors] = useState({});
const [formValid, setFormValid] = useState(false);
async function handleOnSubmit(e) {
e.preventDefault();
console.log("fields", fields);
console.log("errors", errors);
console.log("formValid", formValid);
const formData = {};
[...e.currentTarget.elements].map((field) => {
if (!field.name) return false;
checkValidation([field.name], field.value);
setFields({ ...fields, [field.name]: field.value });
});
if (formValid === false) return false;
try {
const response = await fetch("/api/mail", {
method: "post",
body: JSON.stringify(formData)
});
const body = await response.json();
console.log(body);
} catch (error) {
console.log(error);
}
console.log(formData);
}
function handleValidation(e) {
const name = e.target.name;
const value = e.target.value;
checkValidation(name, value);
}
function checkValidation(name, value) {
if (value === "") {
delete fields[name];
setErrors((errors) => {
return { ...errors, [name]: "Required" };
});
setFormValid(false);
return false;
}
delete errors[name];
setFields({ ...fields, [name]: value });
setFormValid(true);
// Special validation for email
emailValidation(name, value);
console.log("fields on validaton", fields);
console.log("errors on validation", errors);
}
// Email Validation
function emailValidation(name, value) {
const emailRegex = new RegExp(/[a-z0-9._%+-]+#[a-z0-9.-]+\.[a-z]{2,15}/g);
if (name == "email" && emailRegex.test(value) === false) {
delete fields[name];
setErrors((errors) => ({ ...errors, [name]: "Email is not validate" }));
setFormValid(false);
}
}
return (
<div className="mt-5 mt-md-0">
<h1 className="section-title h1-responsive text-center mb-4">
<span>
Contact Us
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 456.99 38">
<defs>
<style
dangerouslySetInnerHTML={{ __html: ".cls-1{fill:#cb0a34;}" }}
/>
</defs>
<title>latest from out videos</title>
<g id="Layer_2" data-name="Layer 2">
<g id="Layer_1-2" data-name="Layer 1">
<path
className="cls-1"
d="M456.28,8.29a4.6,4.6,0,0,0,.29-.59c0-.11.08-.21.11-.31a4.56,4.56,0,0,0,.21-.72l0-.21A6.11,6.11,0,0,0,457,5.6h0a5.5,5.5,0,1,0-9.24,4C422.6,9.89,288,9.52,281.13,9.52h-31c-7.86,0-15.13,2.18-21.14,7.26a30.14,30.14,0,0,0-8.72-5.25c-4.68-1.82-9.42-2-14.32-2H9.24A5.5,5.5,0,1,0,0,5.5v0c0,.05,0,.09,0,.14a3.4,3.4,0,0,0,0,.45c0,.12,0,.23,0,.35l.06.3A1.82,1.82,0,0,0,.22,7,5.48,5.48,0,0,0,4.5,10.9a5.13,5.13,0,0,0,1.12.16c.85,0,1.7,0,2.54,0H209.44a28.85,28.85,0,0,1,19,7.26,28.31,28.31,0,0,0-6.38,9.13L229,38.29,236,27.45h0a28.11,28.11,0,0,0-6.38-9.12A28.8,28.8,0,0,1,241.09,12c4-1.09,8-1,12.14-1H451l.54,0a5.47,5.47,0,0,0,3.21-1l.07,0s0,0,.05-.05a4.53,4.53,0,0,0,.62-.54l.24-.26a6.41,6.41,0,0,0,.39-.52C456.15,8.52,456.21,8.4,456.28,8.29Z"
/>
</g>
</g>
</svg>
</span>
</h1>
<form className="contact_form" method="post" onSubmit={handleOnSubmit}>
<div className="response-status"></div>
<div className="form-group row">
<div className="col-md-6 mb-2">
<label className="mb-1">
Name <span className="text-danger">*</span>
</label>
<input
type="text"
name="name"
className={`form-control ${errors.name ? "is-invalid" : ""}`}
onChange={handleValidation}
/>
{errors.name && <span className="text-danger">{errors.name}</span>}
</div>
<div className="col-md-6 mb-2">
<label className="mb-1">
Email <span className="text-danger">*</span>
</label>
<input
type="text"
name="email"
className={`form-control ${errors.email ? "is-invalid" : ""}`}
onChange={handleValidation}
/>
{errors.email && (
<span className="text-danger">{errors.email}</span>
)}
</div>
<div className="col-md-12 mb-2">
<label className="mb-1">Subject:</label>
<input type="text" name="subject" className="form-control" />
</div>
<div className="col-md-12 mb-3">
<label className="mb-1">
Message <span className="text-danger">*</span>
</label>
<textarea
name="message"
className={`form-control ${errors.message ? "is-invalid" : ""}`}
rows="5"
onChange={handleValidation}
></textarea>
{errors.message && (
<span className="text-danger">{errors.message}</span>
)}
</div>
<div className="col-md-12">
<button
type="submit"
className="btn btn-primary btn-block rounded waves-effect waves-light"
>
Send Message
</button>
</div>
</div>
</form>
</div>
);
}
Sandbox
I cant reset my form on submit or modal close event using formik. I tried using resetForm({}), resetForm(initialValues),resetForm('') nothing works for me.
RestaurantDetails.js
setting initial values
// setting formik initialValues
const setFromikInitialValue = (product_option_groups) => {
let updatedOption = product_option_groups;
product_option_groups.forEach((option, optionIndex) => {
if (option.minimum === 1 && option.maximum === 1) {
//If displaying radio buttons for modifier
//selected option is kept in a key selected_modifier
updatedOption[optionIndex] = {
...option,
selected_modifier: option.product_options[0].id,
};
} else {
//If displaying check box for modifier
//selected_modifier_count key is added to track checked box count
updatedOption[optionIndex] = {
...option,
selected_modifier_count: 0,
};
}
//For each check box selected key is added
//selected is true if check box is checked & if unchecked it is false
option.product_options.forEach((item, itemIndex) => {
updatedOption[optionIndex].product_options[itemIndex] = {
...item,
selected: false,
};
});
});
return updatedOption;
};
const initialValues = {
product_id: selectedFoodItem.id,
options: setFromikInitialValue(modifiersData),
};
render part of formik
{/* Modal */}
<div
className="modal fade dish-modal"
id={`dishModal-${selectedFoodItem.id}`}
tabIndex={-1}
role="dialog"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
data-backdrop="static"
data-keyboard="false"
>
<div className="modal-dialog" role="document">
<div className="modal-content">
<Formik
initialValues={initialValues}
enableReinitialize
//form submition
onSubmit={async (values, { setSubmitting, resetForm }) => {
console.log(values);
setSubmitting(false);
resetForm(initialValues);
window.$(`#dishModal-${selectedFoodItem.id}`).modal("hide");
}}
//yup form validation
validationSchema={Yup.object().shape({
options: Yup.array().of(
Yup.object().shape({
selected_modifier_count: Yup.number()
.min(
Yup.ref("minimum"),
"minimum number of modifier not choosed"
)
.max(
Yup.ref("maximum"),
"maximum number of modifier exeded"
),
})
),
})}
>
{({
isSubmitting,
setFieldValue,
values,
errors,
touched,
resetForm,
}) => (
<Form>
<div className="modal-body">
<button
type="button"
className="close"
data-dismiss="modal"
aria-label="Close"
onClick={() => {
resetForm({});
}}
>
<span aria-hidden="true">×</span>
</button>
<div className="item-details">
<h4>{selectedFoodItem.name}</h4>
<h4>
{dollar}
{selectedFoodItem.price}
</h4>
</div>
<div className="modifiers">
{/* mapping modifier items */}
{values.options && values.options.length
? values.options.map((modifier, optionIndex) => (
<div
key={`${modifier.id}-${optionIndex}`}
className="modifiers"
>
<h5>{modifier.name}</h5>
<h6>
{modifier.minimum || modifier.maximum ? (
errors.options && touched.options ? (
errors.options[optionIndex] &&
touched.options[optionIndex] ? (
<span className="text-danger">
Minimum {modifier.minimum} Maximum{" "}
{modifier.maximum}
</span>
) : (
<span>
Minimum {modifier.minimum} Maximum{" "}
{modifier.maximum}
</span>
)
) : (
<span>
Minimum {modifier.minimum} Maximum{" "}
{modifier.maximum}
</span>
)
) : (
<span className="text-danger">
Minimum {modifier.minimum} Maximum{" "}
{modifier.maximum}
</span>
)}
{modifier.minimum > 0 ? (
<span className="badge badge-light float-right">
Required
</span>
) : null}
</h6>
<ul>
{modifier.minimum === 1 &&
modifier.maximum === 1
? modifier.product_options.map(
(item, itemIndex) => (
<li key={itemIndex}>
<div className="form-check">
<input
defaultChecked={
itemIndex === 0 ? true : false
}
className="form-check-input"
type="radio"
name="temp"
id={`exampleCheck${item.id}`}
onChange={(
event,
value = item.id
) => {
setFieldValue(
`options[${optionIndex}]`,
{
...modifier,
selected_modifier: value,
}
);
}}
/>
<label
className="form-check-label"
htmlFor={`exampleCheck${item.id}`}
>
{item.name}
<span>
{dollar}
{item.price}
</span>
</label>
</div>
</li>
)
)
: modifier.product_options.map(
(item, itemIndex) => (
<li key={`${item.id}-${itemIndex}`}>
<div className="form-check">
<input
type="checkbox"
className="form-check-input"
id={`exampleCheck${item.id}`}
onClick={() => {
if (!item.selected) {
setFieldValue(
`options[${optionIndex}]`,
{
...modifier,
selected_modifier_count:
modifier.selected_modifier_count
? modifier.selected_modifier_count +
1
: 1,
}
);
} else {
setFieldValue(
`options[${optionIndex}]`,
{
...modifier,
selected_modifier_count:
modifier.selected_modifier_count -
1,
}
);
}
setFieldValue(
`options[${optionIndex}][product_options][${itemIndex}]`,
{
...item,
selected: !item.selected,
}
);
}}
defaultChecked={
item.selected ? true : false
}
/>
<label
className="form-check-label"
htmlFor={`exampleCheck${item.id}`}
>
{item.name}
<span>
{dollar}
{item.price}
</span>
</label>
</div>
</li>
)
)}
</ul>
</div>
))
: null}
</div>
<button
type="submit"
className="btn btn-primary continue-btn"
// disabled={isSubmitting || !dirty}
>
Continue
</button>
</div>
</Form>
)}
</Formik>
</div>
</div>
</div>
{/* Modal end */}