How to set a value of nest object value in onChange event - reactjs

I'm trying to set a value of nested object value for the onChange event.
const [state, setState] = useState({
fullname: '',
email:
address: {
fhbca: '',
street: '',
landmark: '',
pincode: '',
}
});
onChange={(e) => { setState({...state, address.fhbca : e.target.value }) }} **<--**
When setting the value of address.fhbca. Using this above statement throws errors Accounts.jsx: Unexpected token, expected "," (120:100). Please correct me and suggest.
how to set a value of this address.fhbca ..?

You need to do like this
onChange={(e) => { setState(({...state, address: {...state.address, fhbca: e.target.value} }})}}

Related

Fonctionnement formulaire contrôlé react?

I have a problem with react. with a form, I created more inputs that are controlled. I created a function that runs when the form changes. which but updates a state. but reacte gives me an error.
VM2529 react_devtools_backend.js:4026 Warning: A component is changing a controlled input to be uncontrolled. This is likely caused by the value changing from a defined to undefined, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component.
handleChange func:
let DefaultValue = {
firsName: '',
lastname: '',
email: '',
message: ''
}
const [output, setOutput] = useState(DefaultValue)
const handleChange = (e) => {
setOutput({ [e.target.name]: e.target.value });
}
thank you for your reply.
setOutput({ [e.target.name]: e.target.value });
This will not be merged with the old state, it will replace the old state. So if they change lastName, the new state will be { lastName: "some string" }, and all other values will be undefined.
Instead, you need to copy all the other values from the old state:
setOutput(prev => ({
...prev,
[e.target.name]: e.target.value
});

Errors state is not updating on first click

I am making a form to update addresses. When the user clicks on the submit button, the form isn't being submitted but after the click the errors are updated correctly and the form submits fine. Here is what im working with
const [errors, setErrors] = React.useState({
firstName: '',
lastName: '',
streetAddress: '',
streetAddress2: '',
city: '',
zipCode: ''
});
const validateRequiredFields = () => {
let inputError = {};
Object.keys(user).forEach(
(key) => {
inputError = { ...inputError, ...RequiredValidation(user, key) };
}
);
setErrors({ ...errors, ...inputError });
The Validation returns inputError which takes in the values and sets to null instead of empty strings if theres no error. When I console.log this it returns the correct format. Also if there is errors, the errors are updated correctly on first click. Having looked into this I saw that most recent state isn't always fetched which I believe is my problem & the other threads I looked into recommended something like this although I don't know if thats how I am supposed to set it up.
setErrors({ ...errors, ...inputError }, () => {
setErrors({ ...errors, ...inputError });
});
Use
setErrors((prevErrors) => ({ ...prevErrors, ...inputError }));

How to get values and add them to dynamic form fields?

I have created a form that can add fields dynamically(add more). I need to auto populate data that is taken through an id of a select field and add them into some input fields. problem is I am getting the expected result but not getting the value added to those specific data fields. It auto populated every dynamic field that is created relevant to its attribute name.
Here's my states
const [productSelectedList, setproductSelectedList] = useState([])
const [inputList, setInputList] = useState({
user_id: '',
agent_id: '',
tonnes_complete: '',
dockets_complete: '',
customer_id: '',
customer_address: '',
spreading_unit: '',
payment_method: '',
spread_completed_data: '',
spread_rate: '',
payment_status: '',
order_status: '',
order_list: [
{
docket_number: '',
operator_id: '',
product_id: '',
product_mix_id: '',
product_docket: '',
quantity: '',
quantity_rate: '',
spread_status: '',
driver_comments: '',
},
],
})
Here's my onchange and how i am selecting the data from an api
const handleChangeProductMix = (e, index) => {
const { name, value } = e.target
const list = { ...inputList } //<-- object, not array
list.order_list[index][name] = value
const product_mix_id = list.order_list[index][name]
axios.get(`api/edit_product_mix/${product_mix_id}`).then((res) => {
if (res.data.status === 200) {
setproductSelectedList(res.data.product_mix)
} else if (res.data.status === 404) {
swal('Error', res.data.message, 'error')
history.push('/products/productmix')
}
})
setInputList(list)
console.log(productSelectedList)
}
const handleChange = (e, index) => {
const { name, value } = e.target
const list = { ...inputList } //<-- object, not array
console.log(list)
list.order_list[index][name] = value
setInputList(list)
}
and here's my input field
<div className="col-lg-4 mb-2">
<div className="form-group">
<label className="pb-2">Product Mix Name</label>
<input className="form-control" type="text" name="product_docket" onChange={(e)=> handleChange(e, i)}
value={productSelectedList.product_docket}
placeholder="Select Customer Code"
/>
</div>
</div>
When the auto populated data is added and when i click add more the data is being duplicated and replaced with recently selected option.
here's how i add fields dynamically
const handleAddInput = (e) => {
e.preventDefault()
setInputList({
...inputList,
order_list: [
...inputList.order_list,
{
docket_number: '',
operator_id: '',
product_id: '',
product_mix_id: '',
product_docket: '',
quantity: '',
quantity_rate: '',
spread_status: '',
driver_comments: '',
},
],
})
}
You are directly modifying React state with these lines in handleChange and handleChangeProductMix:
const list = { ...inputList } //<-- object, not array
list.order_list[index][name] = value
You need to deep clone the inputList state instead, so that you are not directly modifying one of the values on a property of one of the objects in the array on inputList.order_list.
Here are a few different ways to do it:
JSON.parse:
const list = JSON.parse(JSON.stringify(inputList));
Spread syntax (like in handleAddInput):
const list = {
...inputList,
order_list: inputList.order_list.map(o => ({...o})),
};
A simple clone function:
/**
* This is a naive clone implementation, only meant to be used with
* plain objects/arrays and scalar values.
*/
function clone (value) {
if (typeof value !== 'object') return value;
if (value === null) return value;
if (Array.isArray(value)) return value.map(v => clone(v));
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, clone(v)]));
}
const list = clone(inputList);

how to multiple validation in form with react hook

I have a useState problem, I am trying to add an object in the state but value errors, console.log(errors):
{name: "", description: "", price: "", category: "", image: "Image cannot be empty"}
I want all values ​​in one object.
Hope everyone will help me because I am new.
Here is my code:
const [errors, setErrors] = useState({
name: '',
description: '',
price: '',
category: '',
image: '',
});
const [formIsValid, setFormIsValid] = useState(true);
const handleValidation = () => {
//Name
if(!formState.name){
setFormIsValid(false);
setErrors({
...errors,
name: 'Name cannot be empty',
});
}
//category
if(!formCategory.category){
setFormIsValid(false);
setErrors({
...errors,
category: 'Category cannot be empty',
});
}
//Image
if(!image.image){
setFormIsValid(false);
setErrors({
...errors,
image: 'Image cannot be empty',
});
}
return formIsValid;
};
You are trying to make state update based on your previous state. Basically the issue is following:
you want to make few state updates synchronously (when few properties are not valid) and only last update is applied.
So why this happen?
In the code above when errors is equal to initial state and all fields are empty will happen following
setErrors({
...errors,
name: 'Name cannot be empty',
});
is the same as
setErrors({
description: '',
price: '',
category: '',
image: '',
name: 'Name cannot be empty',
});
after that you are entering another if statement and there you are performing another setState operation with the same state, and errors array gone be the same
so this
setErrors({
...errors,
category: 'Category cannot be empty',
});
will be transformed to this
setErrors({
description: '',
price: '',
category: 'Category cannot be empty',
image: '',
name: '',
});
React will schedule all the setState one after each other and as you are assigning object it will just override the last existing one and name property will be cleared.
So it is two way to solve current issue if you want to use object as a state:
Generate object and then execute setState one time with combined object, that contains all the changes:
const [errors, setErrors] = useState({
name: '',
description: '',
price: '',
category: '',
image: '',
});
const handleValidation = () => {
const newErrorsState = {...errors};
let formIsValid = true;
//Name
if(!formState.name){
formIsValid = false;
newErrorsState.name = 'Name cannot be empty';
}
//category
if(!formCategory.category){
formIsValid = false;
newErrorsState.category = 'Category cannot be empty';
}
//Image
if(!image.image){
formIsValid = false;
newErrorsState.image = 'Image cannot be empty';
}
if (!formIsValid) { // if any field is invalid - then we need to update our state
setFormIsValid(false);
setErrors(newErrorsState);
}
return formIsValid;
};
Second way to solve this issue is to use another way to set your state,
You can use function inside of your setState handler. Inside that function you will receive the latest state as parameter and that state will have latest changes.
More information can be found by the link
const [errors, setErrors] = useState({
name: '',
description: '',
price: '',
category: '',
image: '',
});
const [formIsValid, setFormIsValid] = useState(true);
const handleValidation = () => {
//Name
if(!formState.name){
setFormIsValid(false);
const updateNameFunction = (latestState) => {
return {
...latestState,
name: 'Name cannot be empty',
};
}
setErrors(updateNameFunction);
}
//category
if(!formCategory.category){
setFormIsValid(false);
setErrors((prevErrors) => {
return {
...prevErrors,
category: 'Category cannot be empty',
}
});
}
//Image
if(!image.image){
setFormIsValid(false);
setErrors((prevErrors) => {
return {
...errors,
image: 'Image cannot be empty',
};
});
}
return formIsValid;
};
Not sure what you are trying to achieve.
Anyway I think you want to validate multiple fields and get their errors together.
Please look at react-hook-form documentation
Every field that registered to the form will give you his errors "automatically".
In addition, You can add validations in the object of the second argument of register like that :
<Input {...register("firstName", { required: true })} />
List of available validations that you can add
Hope thats help you :) Good luck!
from where formState is coming? and you can use usereducer or react-hook-form library for better approch

React overwrites all states on setState

I have the following setup:
this.state = {
values: {
id: null,
name: "",
unitName: "",
unitCategory: ""
}
};
and when I change a value in a field in a form I have the following handleChange function:
this.setState({
values: {
[e.target.name]: e.target.value
}
});
the problem is - this removes all other values from the values object in my state and only leaves the one that's being modified (i.e. <input name="name" />).
How could I retain all other values?
Edit
The code now looks like:
this.setState(prevState => {
console.log(prevState.values);
return {
values: {
...prevState.values,
[e.target.name]: e.target.value
}
};
});
The console.log(prevState.values) returns:
{id: 2, name: "Humidity", unitName: "himidity", unitCategory: "%"}
Which is how it should be, but when I spread it in values object, I get:
TypeError: Cannot read property 'name' of null
Use spread syntax to spread the other properties into state:
this.setState(prevState => ({
values: {
...prevState.values,
[e.target.name]: e.target.value
}
}));
This also utilizes the callback with setState to ensure the previous state is correctly used. If your project doesn't support object spread syntax yet, you could use Object.assign:
values: Object.assign({}, prevState.values, {
[e.target.name]: [e.target.value]
})
This does essentially the same thing. It starts with an empty object to avoid mutation, and copies prevState.values's keys and values into the object, then copies the key [e.target.name] and its value into the object, overwriting the old key and value pair.
Also, I'm not sure why you're doing all this:
this.state = {
values: {
id: this.state.values.id ? this.state.values.id : null,
name: this.state.values.name ? this.state.values.name : "",
unitName: this.state.values.unitName ? this.state.values.unitName : "",
unitCategory: this.state.values.unitCategory? this.state.values.unitCategory: "
}
};
When you set initial state in the constructor, just give the initial value, your ternary operator will never give you the true condition:
this.state = {
values: {
id: null,
name: '',
unitName: '',
unitCategory: ''
}
};

Resources