Updating a nested state setState in react - reactjs

Cannot figure out why this state will not update, there are other values other than personalValues that is why it is nested.
The state
this.state = {
personalValues: [
{targetLevel: 0},
{firstCommission: 0.36},
{cancels: 0.10},
{averagePolicy: 1150},
{productionWeeks: 48},
{presPerWeek: 15},
{closingRate: 0.40},
{appsPerWeek: 6}
]
The handler I tried
handleFormChange(event){
this.setState({[event.target.name]: event.target.value})
}
The Component
const personalFormValues =
{
class: ["firstCommission", "cancels", "averagePolicy",
"productionWeeks", "presPerWeek", "closingRate",
"appsPerWeek"],
};
var className = personalFormValues.class[i];
<TextField
className={className}
type="number"
placeholder={personalFormValues.placeholder[i]}
onChange={this.handleFormChange}
onBlur={this.changeValues}
name={className}
fullWidth={true}
pattern="[0-9]*"
required />

In terms of the state there is only a single value this.state.personalValues. To React it doesn't matter if that value is an array of objects. Instead simply create a variable that stores the current value let obj = this.state.personalValues. Then iterate through obj until you find a match on the key with event.target.name and set the value of that obj to event.target.value. Or more simply doesn't use an array and use an object directly:
this.state = {
personalValues: {
targetLevel: 0,
firstCommission: 0.36,
cancels: 0.10,
averagePolicy: 1150,
productionWeeks: 48,
presPerWeek: 15,
closingRate: 0.40,
appsPerWeek: 6
}
Then you could just do:
let obj = this.state.personalValues;
obj[event.target.name] = event.target.value;
this.setState({personalValues: obj});

React checks elements of this.state for shallow pointer equivalency before triggering a re-render.
Therefore, if you in-place-modify a recursive data-structure, then React will fail to notice a change, because shallow comparison between previous and next state object shows no difference.
Thus you cannot write to a recursive structure, nor do:
this.setState(prevState => ({personalValues: Object.assign(prevState.personalValues, nextValues)}));
because Object.assign performs in-place modification.
You must do either of:
this.setState(prevState => ({personalValues: Object.assign({}, prevState.personalValues, nextValues)}));
this.setState(prevState => ({personalValues: {...prevState.personalValues, ...nextValues}}));
This will create a new object and copy the data-structure in it. Be aware that only the first level object is created anew.
Recursive copying gets ugly; see https://stackoverflow.com/a/43041334/1235724
Nonetheless, if you have a very large data structure stored in your state,
you may want to use in-place modification together with this.forceUpdate(),
eschewing copying altogether.

Do you need personalValues to be an array? If you specify it like this, that should work:
state = {
personalData: {
targetLevel: 0,
firstCommission: 0.36
....
}
}
handleFormChange({target}){
this.setState(prevState => ({
personalValues: {
...prevState.personalValues,
[target.name]: target.value
}
})
}

Better way to copy old state to a new variable then change the values if you want and then update the state value with that newly created type variable.
let personalValuesCopy = this.state.personalValues;
personalValues.dynamicKey = updated values // Update values may be using onChange events or some custom data
this.setState({ personalValues : personalValuesCopy }) // Update state with new object

Related

useState non-instantaneous updates break function

I have a sumButtonsDict state variable that stores a dictionary of objects. I've also built a simple addSumButton() function add a new object into the sumButtonsDict:
const [sumButtonsDict, setSumButtonsDict] = useState({})
function addSumButton(sum) {
const dictKey = Object.keys(sumButtonsDict).length
const sumButtonDict = {
sum: sum,
isActive: false
}
setSumButtonsDict(prevState => ({...prevState, [dictKey]: sumButtonDict}))
}
As you can see, the function stores every new dictionary item at a key corresponding to the index it's on (e.g., first item has key 0, second item has key 1...), but the correct key is derived from the count of objects already existing in sumButtonsDict.
When the component mounts, I add 5 new buttons using the following:
useEffect(() => {
addSumButton(10)
addSumButton(25)
addSumButton(50)
addSumButton(100)
addSumButton(250)
}, [])
but only 1 ends up existing in sumButtonsDict. I suspect this is because setState() doesn't update the state variable immediately, and hence when I call Object.keys(sumButtonsDict).length it keeps on returning 0 even though the addSumButton() has run multiple times before.
How can I get around this?
You're already using the function version of setSumButtonsDict to get the previous state, which is the right thing to do. You just need to move the other bits of code into the function too, so the entire calculation uses prevState:
function addSumButton(sum) {
setSumButtonsDict(prevState => {
const dictKey = Object.keys(prevState).length;
const sumButtonDict = {
sum: sum,
active: false,
}
return {...prevState, [dictKey]: sumButtonDict}
});
}

React array state update

I am getting a book list from database and is stored in a state variable Book list also has book price field
const [books, setBooks]=useState([])
setBooks(data)
Each books object in the array has properties like BookName, Author , Price, Discount
I have rendered a html table like follows
return ( <div>
{books.map((x,i) => ( <tr>
<td>x.bookName</td>
<td>x.price</td>
<td><MyCustomTextInput onChange={e => handleChange(e, x.id)} value={x.discount}></MyCustomTextInput></td>
<tr></div>);
the sample code for MyCustomTextInput is as follows
function MyCustomTextInput(props)
{ return (<div><TextInput></TextInput> </div>)
} exports default MyCustomTextInput
The code where I update the price for corresponding object in "books" array is as follows
function handleChange(x,id){
var obj = books[id];
obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
}
Every thing works properly except the price after discount is not reflecting on the UI. though its getting updated properly in the array but not reflecting on the UI.
any help....
Experts -
This is setting a value:
function handleChange(x, id){
var obj = books[id];
obj.price = obj.price - e.target.value;
}
But it's not updating state. The main rule to follow here is to never mutate state directly. The result of bending that rule here is that this update never tells React to re-render the component. And any re-render triggered anywhere else is going to clobber the value you updated here since state was never updated.
You need to call setBooks to update the state. For example:
function handleChange(x, id){
setBooks(books.map(b =>
b.id === id ? { ...b, price: b.price - parseFloat(e.target.value) } : b
));
}
What's essentially happening here is that you take the existing array in state and use .map() to project it to a new array. In that projection you look for the record with the matching id and create the new version of that record, all other records are projected as-is. This new array (the result of .map()) is then set as the new updated state.
There are of course other ways to construct the new array. You could grab the array element and update it the way you already are and then combine it with a spread of the result of a .filter to build the new array. Any construction which makes sense to you would work fine. The main point is that you need to construct a new array and set that as the new state value.
This will trigger React to re-render the component and the newly updated state will be reflected in the render.
You need to setBooks to update state books.
function handleChange(x, id) {
setBooks(
books.map((item) =>
item.id === id ? { ...item, price: item.price - parseFloat(e.target.value) } : item,
),
);
}
To achieve that, you need to call setBooks after changing the price within handleChange method to re-render the component with the newly updated state.
It's simply like the following:
function handleChange(x,id){
var obj = books[id];
obj.price = obj.price - e.target.value; //I am correctly getting discount in e.target.value
setBooks([...books]);
}

updating one value in State array react native

Trying to update one element of an array in this.state I'm getting an (expected ,) error but cant see where I've gone wrong. Do I need to create a temporary array update that, then assign the whole array back to the state
This is essentially what I have
this.state = { array: ['a', 'b', 'c'] };
onBack() {
this.setState({
array[2]: 'something'
});
}
You can't update the state like this.
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
Read React docs.
You can do something like this :
let newArray = [...this.state.array];
newArray[2] = 'somethingElse';
this.setState({array: newArray});
The above example is using Spread Syntax.
There are multiple ways to modify state, but all the ways should ensure that data is treated as immutable. You can read more about handling state in React here
Manoj's answer will work, there is another way using the updater function (funtinal setState) for setState (which you can read about here)
onBack() {
this.setState(prevState => {
let newArray = prevState.array
newArray[2] = "something"
return { array: newArray }
});
}
Use Object.assign({});
let array= Object.assign({}, this.state.array); //creating copy of object
array[2]="something"
this.setState({array});
If you are bother to set the new updated array on your currentArray using spread function, you may use Array.from(duplicateArray)
let duplicateArray = [...currentArray] //duplicate the array into new array
duplicateArray[index].value = 'x'; // update the field you want to update
setCurrentArray(Array.from(duplicateArray )) //set the new updated array into currentArray
I bother with this issue and I just search about and I saw this idea, and it works!
https://github.com/facebook/react-native/issues/21734#issuecomment-433885436
using hooks
const [totalSet, setTotalSet] = useState(null);
console.log(totalSet); // {sets: Array(3), subscribed: false}
setTotalSet(datas => ({
...datas,
sets: updatedSet,
}));
Create a temporary array update that, then assign the whole array back to the state
var arr = [];
arr = this.state.array;
arr[2] = "something";
this.setState({
array : arr
});

How do i change a property in state whose name is the value of a variable?

I have a quick question about reactjs that hopefully someone can answer. Lets keep it short and sweet.
I have a property in state:
this.state = {
property: []
}
This property is an array. I would like to change one of the properties inside of the array:
this.setState({
property:{
index: value,
}
});
I have 'index' passed as a variable into my state changing function. Index is a number. The value of the index will be something such as '0', '1', '2'... etc.
Doing what I did above will set the value of the property 'index' which is not what I want. How would I instead change the value of the property that has the name stored in the variable 'index'?
You create a copy, assuming index being the position where you want the change to happen and value being the new value you can use this
this.setState({
property: this.state.property.map((anItem, i) => (i === index) ? value : anItem)
});
If your real state happens to have more properties you'll need to use this
this.setState({
...this.state,
property: this.state.property.map((anItem, i) => (i === index) ? value : anItem)
});
I think you are looking for something like this:
const index = 'dummy'
const value = 69
this.setState({
property:{
[index]: value,
}
});
// after the state updated
console.log(this.state.property.dummy); // 66
Do not directly mutate anything in this.state. You are supposed to let React handle that, so make a shallow copy of it and manipulate that.
const newProperty = [...this.state.property];
newProperty[index] = value;
this.setState({property: newProperty});
If you want to use ES5 to make the copy, do this instead:
const newProperty = this.state.property.slice();

Update nth item in array in state in parent from child in React?

In my top level component I have a function to update state. I pass this down to different child elements so that they can update my main state.
In my top level component:
updateValue(item, value) {
this.setState({[item]: parseInt(value)});
}
This has worked so far however now I need to update the nth item in an array.
My top level state is like this:
this.state = {
chosenExercises: [
'Bench press',
'Squat',
'Pull up'
]
};
And in my child component Im trying to do something like:
this.props.updateValue('chosenExercises'[1], 'New exercise');
So that my state would then be:
this.state = {
chosenExercises: [
'Bench press',
'New exercise',
'Pull up'
]
};
Am I going about this the correct way? Or should my state be key value pairs?
this.state = {
chosenExercises: {
0: 'Bench press',
1: 'New exercise',
2: 'Pull up'
}
};
This would potentially solve some of my problems of making the exercises easier to target but Im not sure which is best practice.
Since the chosenExercises can be multiple it makes sense to make it as an array, however you need to update your state differently. Instead of passing the index of the array element to update, you should actually make a copy of the array, update it in the child element and then send the updated array to the parent.
You could do something like:
In Child:
updateValue = (item, index, value) => {
let newValue = [...this.props[item].slice(0, index), value, ...this.props[item].slice(index + 1)];
this.props.updateValue(item, newValue);
}
The thing with this is that your state has to remain immutable so you have to provide a new Array to update in your state. So you'll end up with something like:
this.updateValue('chosenExercises', 1, 'New exercise');
updateValue(item, index, value) {
const newArray = this.state[item].slice();
newArray[index] = value;
this.setState({ [item]: newArray });
}
The array.slice() function creates a new Array, in which you update the value by its index. Afterwards you update your component state with the new array.
If you happen to do this more often, React created an immutability helper for these things. You can read more about it here. This would let you do something like:
import update from 'react-addons-update';
this.setState({
[item]: update(this.state[item], {[index]: {$set: value } })
});
It can be done with this in the top level component:
updateValue(item, value, options) {
if (options.isChosenExercises === true) {
this.setState((prevState) => {
let newchosenExercises = prevState.chosenExercises.slice();
newchosenExercises[item] = value;
return {chosenExercises: newchosenExercises};
});
} else {
this.setState({[item]: parseInt(value)});
}
}
For normal uses pass an empty object as the last parameter:
this.props.updateValue('setLength', e.target.value, {})}
But when you want to update the exercise array pass an object with isExercises set to true.
chooseThisExercise() {
this.props.updateValue(numberInTheArrayToChange, newExercise, {isChosenExercises: true});
}

Resources