Change Boolean value based on the previous state value - reactjs

I have created the toggle function where it will change the Boolean value to false. And I am passing that handler function to button, now I am trying to achieve the same by using previous value, the problem I am facing here is I am having a mock data which will have the following structure {[{}]} inside one object I'll have an array inside that I'll have another objects. I have posted the mock and older implementation by selecting only one value from the mock, could any one guide me how to change the boolean value for the mock which I have. Thanks in advance.
const custDetail = {
customers: [
{
name: "Abc",
isCreated: true,
},
{
name: "bcd",
isCreated: true,
},
{
name: "Dec",
isCreated: true,
},
],
};
Code:
const [creatingCust, setCreatingCust] = useState([custDetail])
const custData = [...creatingCust]
custData[0].customers[0].isCreated = false
setCreatingCust(custData)
//trying to use prevState but I am getting undefined
const onClick = () => {
setCreatingCust(prevState => ({isCreated:!prevState.customers[0].isCreated}))

Shallow copy the state, and all nested state, that is being updated. I suggest using the customer name property to match the customer element in the customers array that you want to update. Use Array.prototype.map to create a new array reference.
I suggest also just storing custDetail in the creatingCust state. I don't a reason to nest it in an array.
Example:
const [creatingCust, setCreatingCust] = useState(custDetail);
const onClick = (name) => {
setCreatingCust(prevState => ({
...prevState,
customers: prevState.customers.map(
customer => customer.name === name
? {
...customer,
isCreated: !customers.isCreated
}
: customer
),
}));
};
If you must have creatingCust be an array the process is similar, but instead of shallow copying into a new object you shallow copy into a new array.
const onClick = (name) => {
setCreatingCust(prevState => [{
...prevState[0],
customers: prevState[0].customers.map(
customer => customer.name === name
? {
...customer,
isCreated: !customers.isCreated
}
: customer
),
}]);
};

Related

Is this the correct way to update a propery in objects array state

I've got the code below and i wanna update name property in the object that has id 1. I'm updating with the code objArray[1].name = "Xxx". It perfectly works but is this correct? Should i use prevState with setObjArray. That looked so much easier what you think?
const [objArray, setObjArray] = useState([
{
id:1,
name:"Eren"
},
{
id:2,
name:"Eren2"
},
{
id:3,
name:"Eren3"
}
])
No this is not advisable. You have the useState second array element (setObjArray) for updating state. Read documentation for React useState . There are two basic ways but there isn't much difference. First method;
const changeName = (id, newName) => {
// map through the array and change the name of the element with the id you are targeting
const changedArr = objArray.map((element) => {
if (element.id === id) {
return {
...element,
name: newName,
};
} else {
return element;
}
});
// set the returned array as you new state
setObjArray(changedArr)
};
Second method;
You have access to the previous state. This way you can make changes on the previous state and return the new array as your new state.
const newChangeName = (id, newName) => {
setObjArray((prev) => {
// map through the array and change the name of the element with the id you are targeting
// set the returned array as you new state
return prev.map((element) => {
if (element.id === id) {
return {
...element,
name: newName,
};
} else {
return element;
}
});
});
};
Hope this helped.
There are many ways to do this. Let me share one way to do this:
Make a shallow copy of the array
let temp_state = [...objArray];
Make a shallow copy of the element you want to mutate
let temp_element = { ...temp_state[0] };
Update the property you're interested in
temp_element.name = "new name";
Put it back into our array. N.B. we are mutating the array here, but that's why we made a copy first
temp_state[0] = temp_element;
Set the state to our new copy
setObjArray(temp_state);

How do I change the state of an array of objects in React?

I'm trying to change the state of an array containing an object whenever something is typed inside of an input. The state of the array I'm trying to change looks like this:
const defaultCV = {
personalInfo: {
firstName: "",
lastName: "",
title: "",
about: "",
},
education: [
{
id: uniqid(),
university: "",
city: "",
degree: "",
subject: "",
from: "",
to: "",
},
],
Specifically, I want to change the state of the 'education' section. My current, non-working code looks like this:
const handleEducationChange = (e) => {
setCV((prevState) => ({
...prevState,
education: [
{
...prevState.education,
[e.target.id]: e.target.value,
},
],
}));
};
When I type in the input and the function is triggered, I get the error "Warning: Each child in a list should have a unique "key" prop." I've been trying to make this work for the past few hours, any help as to what I'm doing wrong would be appreciated.
Are you using the Array.map() method to render a list of components? That is a common cause of that error. For example if you are mapping the education array.
You can fix by using the object id as the key since that is already generated for each object:
defaultCV.education.map(institution => {
return <Component key={institution.id} institution={institution} />
}
You are destructuring an array in to an object that will not work
education: [{ // this is the object you are trying to restructure into
...prevState.education, // this here is an array in your state
[e.target.id]: e.target.value,
}, ],
}));
Suppose you render this input field:
<input id='0' type='text' name='university' value={props.value} />
Your event.target object will include these props:
{id = '0', name = 'university', value = 'some input string'}
When updating the state, you have to first find the array item (object), that has this id prop, then you can change its 'name' property and return the new state object.
This worked for me:
setCV(prevState => {
const eduObjIdx = prevState.education.findIndex(obj => obj.id === +e.target.id)
prevState.education[eduObjIdx][e.target.name] = e.target.value
return {
...prevState,
education: [
...prevState.education.splice(0, eduObjIdx),
prevState.education[eduObjIdx],
...prevState.education.splice(eduObjIdx + 1),
],
}
})
Make sure you send the current state of the input value when rendering the component:
<Component value={state.education[currentId].id} />
where currentId is the id of the university you are rendering.
If you render it mapping an array, don't forget the key (answered at 0), otherwise you'll get the above error message.
This way you don't mutate the whole education array. May not be the best solution though.

array declaration in this.state React

**I'm trying to create an array with 5 values which I could use with nameArray[number].
I think that the declaration of the array is wrong but I don't know how I can fix it.
My idea is that: I have 5 buttons, when I click one of this, only one value of the 5 values in the state array change from false to true.
**
constructor(props) {
super(props);
this.state = {
activeButtons: [false, false, false, false, false]
};
}
cliccato = (e) => {
e.preventDefault();
const number = parseInt(e.target.id);
this.setState(
{
activeButtons: !this.state.activeButtons[number],
},
() => {
console.log(" "+ this.state.activeButtons[number]);
}
);
}
You're updating your state's activeButtons with a single boolean value, rather than an updated array.
You need to generate a new array and modify only the relevant element:
const newArray = [...this.state.activeButtons];
newArray[number] = !newArray[number];
this.setState({
activeButtons: newArray,
});
Declaration of the array is fine. You can make it shorter with Array(5).fill(false).
It's setting state part that needs work. In your current code, you are setting the state to the alternate of a boolean value, instead you need to set it to an array.
this.setState(prevState => ({
activeButtons: prevState.activeButtons.map((val, index) => {
if(index === number) {
return !val;
}
return val;
})
}));
Also, using the functional set state form here
It's because you're overwriting activeButtons every time with only one element value. You need to preserve the other values each time you want to update an element.
Using an Object would be a more graceful data structure in this case though.
this.setState(
{
activeButtons: {
...this.state.activeButtons
[number]: !this.state.activeButtons[number]
}
)

React.useState is changing initialValues const

I'm experiencing some odd behavior with react's useState hook. I would like to know why this is happening. I can see a few ways to sidestep this behavior, but want to know whats going on.
I am initializing the state with the following const:
const initialValues = {
order_id: '',
postal_code: '',
products: [
{
number: '',
qty: ''
}
]
}
const App = (props) => {
const [values, setValues] = React.useState(initialValues);
...
products is an array of variable size. As the user fills in fields more appear.
The change handler is:
const handleProductChange = (key) => (field) => (e) => {
if (e.target.value >= 0 || e.target.value == '') {
let products = values.products;
products[key][field] = e.target.value;
setValues({ ...values, products });
}
}
What I am noticing is that if I console log initialValues, the products change when the fields are changed. None of the other fields change, only inside the array.
Here is a codepen of a working example.
How is this possible? If you look at the full codepen, you'll see that initialValues is only referenced when setting default state, and resetting it. So I don't understand why it would be trying to update that variable at all. In addition, its a const declared outside of the component, so shouldn't that not work anyway?
I attempted the following with the same result:
const initialProducts = [
{
number: '',
qty: ''
}
];
const initialValues = {
order_id: '',
postal_code: '',
products: initialProducts
}
In this case, both consts were modified.
Any insight would be appreciated.
Alongside exploding state into multiple of 1 level deep you may inline your initial:
= useState({ ... });
or wrap it into function
function getInitial() {
return {
....
};
}
// ...
= useState(getInitial());
Both approaches will give you brand new object on each call so you will be safe.
Anyway you are responsible to decide if you need 2+ level nested state. Say I see it legit to have someone's information to be object with address been object as well(2nd level deep). Splitting state into targetPersonAddress, sourePersonAddress and whoEverElsePersonAddress just to avoid nesting looks like affecting readability to me.
This would be a good candidate for a custom hook. Let's call it usePureState() and allow it to be used the same as useState() except the dispatcher can accept nested objects which will immutably update the state. To implement it, we'll use useReducer() instead of useState():
const pureReduce = (oldState, newState) => (
oldState instanceof Object
? Object.assign(
Array.isArray(oldState) ? [...oldState] : { ...oldState },
...Object.keys(newState).map(
key => ({ [key]: pureReduce(oldState[key], newState[key]) })
)
)
: newState
);
const usePureState = initialState => (
React.useReducer(pureReduce, initialState)
);
Then the usage would be:
const [values, setValues] = usePureState(initialValues);
...
const handleProductChange = key => field => event => {
if (event.target.value >= 0 || event.target.value === '') {
setValues({
products: { [key]: { [field]: event.target.value } }
});
}
};
Probably the simplest move forward is to create a new useState for products which I had started to suspect before asking the question, but a solution to keep the logic similar to how it was before would be:
let products = values.products.map(product => ({...product}));
to create a completely new array as well as new nested objects.
As #PatrickRoberts pointed out, the products variable was not correctly creating a new array, but was continuing to point to the array reference in state, which is why it was being modified.
More explanation on the underlying reason initialValues was changed: Is JavaScript a pass-by-reference or pass-by-value language?

Clearing inputs in React

I have a component in which I create a new post. I have a state used to configure and create a form. The main object in the state is called formControls and inside each element looks something like this:
title: {
elementType: "input",
elementConfig: {
type: "text",
placeholder: "Title"
},
value: "",
validation: {
required: true
},
valid: false,
isTouched: false
}
Then I have a submit handler in which I create a new post and I try to clear the inputs. I m using 2 way binding so I try to clear by looping through state, make copies and update the values for each elements in the formControls : title, author and content like this:
for (let key in this.state.formControls) {
const updatedState = { ...this.state.formControls };
const updatedInput = { ...this.state.formControls[key] };
updatedInput.value = "";
updatedState[key] = updatedInput;
console.log(updatedState);
this.setState({
formControls: updatedState
});
}
The things is that it only clears the last element in the form (textarea). I console logged updatedState and in each iteration it clears the current input but in the next iteration the previous cleared input has again the value before clearing so only the last element is cleared in the end. If i move const updatedState = { ...this.state.formControls };
outside the for loop is behaves as it should. Does this happen because of async operation of setState() and it doesn t give me the right previous state when I try to update in each iteration?
I was hoping that maybe someone could help me understand why is like this. I would post more code but is quite long.
The data available to you in the closure is stale after the first call to setState. All iterations in the for .. in block will be run with the "old" data, so your last iteration is the one which is actually setting the fields to the values as they were when the for .. in loop began.
Try calling setState only once and put all your required changes into that.
const updatedFormControls = { ...this.state.formControls };
for (let key in this.state.formControls) {
const updatedInput = { ...updatedFormControls[key] };
updatedInput.value = "";
updatedFormControls[key] = updatedInput;
}
console.log(updatedFormControls);
this.setState({
formControls: updatedFormControls
});
Another way to do the same thing might look like this:
this.setState(state => ({
...state,
formControls: Object.keys(state.formControls).reduce(
(acc, key) => ({
...acc,
[key]: { ...state.formControls[key], value: '' }
}),
{}
)
});

Resources