The state of array values is not changing i have tried many times but still not succeeded is there a way that i missed somehow here is the attached code highlighting the problem. If anyone can help it would be much appreciated.
Class component handling form stepper
constructor(props) {
super(props);
this.state = {
step: 1,
propertyID:0,
responseMessage:"",
loading:false,
name:"",
address: "",
county:"",
city: "",
zipcode: "",
type: "",
specifictype: "",
formValues:[{beds:"",rent:"",numUnits:0,squarefeet:""}]
};
}
handleFormChange(i, e) {
let formValues = this.state.formValues;
console.log(formValues);
formValues[i][e.target.name] = e.target.value;
this.setState({ formValues });
}
render() {
const { step } = this.state;
const {
address,
name,
specifictype,
city,
county,
zipcode,
type,
formValues,
responseMessage
} = this.state;
const values = {
address,
name,
specifictype,
city,
zipcode,
type,
county,
formValues,
responseMessage
} ;
switch (step) {
case 3:
return(
<PropertyType
values={values}
add={this.addFormFields.bind(this)}
remove = {this.removeFormFields.bind(this)}
handleChange={this.handleFormChange}
prevStep={this.prevStep}
nextStep={this.getandSend}
/>
)
}
}
then here is the code of the component that handles the dynamic input fields.I had to reduce the code other parts are working fine do not worry about values,add,remove,prevStep and next step those are working just fine the problem is how i handle the change
function PropertyType({values,add,remove,handleChange,prevStep,nextStep}) {
return(
<>
<form action="">
{values.formValues.slice(0,5).map((element, index) => (
<div className="w-full md:w-9/12 flex mx-auto">
<div className="mb-6 mr-3">
<label
className="block mb-2 text-coolGray-800"
htmlFor=""
>
Beds
</label>
<select defaultValue={element.beds} onChange={e => handleChange(index, e)} className="block w-full p-3 leading-5 text-coolGray-900 border border-coolGray-200 rounded-lg shadow-md placeholder-coolGray-400 focus:outline-none focus:ring-2 focus:ring-color-main focus:ring-opacity-50 bg-white" aria-label="Default select example">
<option selected>Select bedrooms</option>
<option value="0">Studio</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
<option value="4+">More than 4</option>
</select>
</div>
<div className="mb-2 mr-3">
<label
className="block mb-2 text-coolGray-800 font-medium"
htmlFor=""
>
Number of units
</label>
<input
defaultValue={element.numUnits}
onChange={e => handleChange(index, e)}
className="appearance-none block w-full p-3 leading-5 text-coolGray-900 border border-coolGray-200 rounded-lg shadow-md placeholder-coolGray-400 focus:outline-none focus:ring-2 focus:ring-color-main focus:ring-opacity-50"
type="number"
placeholder="10"
/>
</div>
<div className="mb-2 mr-3">
<label
className="appearance-none block mb-2 text-coolGray-800 font-medium"
htmlFor=""
>
Squarefeet
</label>
<input
defaultValue={element.squarefeet}
onChange={e => handleChange(index, e)}
className="appearance-none block w-full p-3 leading-5 text-coolGray-900 border border-coolGray-200 rounded-lg shadow-md placeholder-coolGray-400 focus:outline-none focus:ring-2 focus:ring-color-main focus:ring-opacity-50"
type="number"
placeholder="2000"
/>
</div>
<div className="mb-2 mr-3">
<label
className="block mb-2 text-coolGray-800 font-medium"
htmlFor=""
>
Rent
</label>
<input
defaultValue={element.rent}
onChange={e => handleChange(index, e)}
className="appearance-none block w-full p-3 leading-5 text-coolGray-900 border border-coolGray-200 rounded-lg shadow-md placeholder-coolGray-400 focus:outline-none focus:ring-2 focus:ring-color-main focus:ring-opacity-50"
type="number"
placeholder="30,000"
/>
</div>
{
index ?<div className="mb-2 mr-3 mt-8">
<button className=" inline-block py-3 px-7 mb-4 w-full text-base text-white font-medium text-center leading-6 bg-red-500/50 rounded-md shadow-sm
" onClick={remove}>Remove</button> </div>
: null
}
</div>
))}
</>
)
}
You need to update the reference of the formValues array with spread operator.
handleFormChange(i, e) {
let formValues = this.state.formValues;
console.log(formValues);
formValues[i][e.target.name] = e.target.value;
this.setState({ formValues: [...formValues ]});
}
you have to make a copy of reference and then update as below:
handleFormChange = (i, e) => {
const formValues = [...this.state.formValues];
const res = formValues.map((val,idx)=>{
if(idx == i){
return ({...val, [e.target.name]: e.target.value})
}
return val
})
this.setState({ ...this.state, formValues: res});
}
finally found the answer for anyone who might need it
class Apps extends React.Component {
constructor(props) {
super(props)
this.state = {
formValues: [{ name: "", email : "" }]
};
}
handleChange(i, e) {
let formValues = this.state.formValues;
formValues[i][e.target.name] = e.target.value;
this.setState({ formValues });
}
render() {
const {formValues} = this.state;
return (
<PropertyType values={formValues} handleChange={this.handleChange.bind(this)} />
);
}
}
Here is the propertytype component
{values.map((element, index) => (
<div className="form-inline" key={index}>
<label>Name</label>
<input type="text" name="name" value={element.name || ""} onChange={e => handleChange(index, e)} />
<label>Email</label>
<input type="text" name="email" value={element.email || ""} onChange={e => handleChange(index, e)} />
{
index ?
<button type="button" className="button remove" onClick={() => remove(index)}>Remove</button>
: null
}
</div>
))}
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 Am populating values of my input field from JSON data what am getting from back-end, now there is an edit button on UI by on click on that button I am enabling my input field but not able to type inside as I am setting some value
I want to write inside the input once I have made them editable.
const { register, handleSubmit, errors } = useForm();
const [disabled, setdisabled] = useState(false);
const [editBtn, seteditBtn] = useState(true);
<form onSubmit={handleSubmit(onSubmit)}>
{editBtn === true && (
<div align="right">
<button
className="btn white_color_btn"
type="button"
onClick={edit}
>
Edit
</button>
</div>
)}
{editBtn === false && (
<button className="btn white_color_btn" type="submit">
Save
</button>
)}
<div className="row">
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
disable
id="firstName"
name="firstName"
value={dataItems.firstname}
disabled={disabled ? "" : "disabled"}
ref={register({ required: true })}
/>
{errors.firstname && (
<span className="text-danger">first name required</span>
)}
<br />
<label htmlFor="emp_designation">First name</label>
</div>
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
disabled
id="lastname"
name="lastname"
value={dataItems.lastname}
disabled={disabled ? "" : "disabled"}
ref={register({ required: true })}
/>
{errors.lastname && (
<span className="text-danger">last name required</span>
)}
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</form>
On click of edit
const edit = () => {
setdisabled(true);
};
Code sandbox
You need to make your input as a controlled component and write onChange handlers which will update the state. This will allow you to edit the input field values. Demo
const [disabled, setdisabled] = useState(false);
const [name, setName] = useState(empData.item.name) // setting default name
const [lastname, setLastname] = useState(empData.item.lastname) // default lastname
const edit = () => {
setdisabled(true);
};
return (<div className="container-fluid">
<div align="right">
<button className="btn" onClick={edit}>
Edit
</button>
</div>
<div className="row">
<div>
<input
type="text"
disable
id="item.value"
value={name}
onChange={(e) => {
setName(e.target.value)
}}
disabled={disabled ? "" : "disabled"}
/>
<br />
<label htmlFor="name">Name</label>
</div>
<div>
<input
type="text"
disabled
id={"lastname"}
value={lastname}
onChange={(e) => {
setLastname(e.target.value)
}}
disabled={disabled ? "" : "disabled"}
/>
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</div>);
Your input is controlled by the value you are giving to it. ie: Its value is always for example empData.item.name.
And you are not providing a change handler to handle the change.
Try adding something like this:
function myChangeHandler(e){
setEditedValueSomeHow(e.target.value);
}
<input
// ...
onChange={myChangeHandler}
/>
Read more about uncontrolled components
PS: you should have had a warning message in your console like this one:
Edit:
You are using react-hook-form to manage your form but at the same time giving values to your inputs.
Please refer to this link to initialize your form values.
short story:
Remove value form your input.
Pass an object to useForm hook containing initial values.
const { register, handleSubmit, errors } = useForm({
defaultValues: {
firstName: "steve",
lastname: "smith"
}
});
Here is a working fork for your codesandbox
In order to make the input editable, you need to update a local state which controlls the input value. As suggested by you in the comments, you are using graphql to get the data, you can make use of useEffect to set the data in state and then on click of edit, update the localState
export default function App() {
const { register, handleSubmit, errors } = useForm();
const [disabled, setdisabled] = useState(true);
const [editBtn, seteditBtn] = useState(true);
const { loading, data } = useQuery("some qraphql query here"); // getting data from graphql
const [formData, setFormData] = useState({});
useEffect(() => {
setFormData(data);
}, [data]);
const edit = () => {
setdisabled(false);
seteditBtn(false);
};
const onSubmit = () => {
console.log(formData);
// submit data using formData state.
};
const handleChange = e => {
const name = e.target.name;
const value = e.target.value;
console.log(name, value);
setFormData(prev => ({ ...prev, [name]: value }));
};
return (
<div className="container-fluid">
<form onSubmit={handleSubmit(onSubmit)}>
{editBtn === true && (
<div align="right">
<button
className="btn white_color_btn"
type="button"
onClick={edit}
>
Edit
</button>
</div>
)}
{editBtn === false && (
<button className="btn white_color_btn" type="submit">
Save
</button>
)}
<div className="row">
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="firstname"
name="firstname"
onChange={handleChange}
value={formData.firstname}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.firstname && (
<span className="text-danger">first name required</span>
)}
<br />
<label htmlFor="emp_designation">First name</label>
</div>
<div className="form-group col-6 col-sm-6 col-md-6 col-lg-4 col-xl-4">
<input
type="text"
id="lastname"
name="lastname"
value={formData.lastname}
onChange={handleChange}
disabled={disabled}
ref={register({ required: true })}
/>
{errors.lastname && (
<span className="text-danger">last name required</span>
)}
<br />
<label htmlFor="lastname">Lastname</label>
</div>
</div>
</form>
</div>
);
}
Working mock demo