How to change state inside nested object in React - javascript

Hi i have parrent and child component. Parrent component is receiving data from server. This data are saved in state.data. Now, when i do action in child component, it should be call method from parrent controller. This is working now. Problem is inside this method which i am calling. I am receiving id as parameter. This data in parrent have list of items (packages) and every item has id. I need to update only one of them by id (or other way i don't know right way). Please how i can do it? I need to update isOpen state only that one item i open by clicking on button in child component
My method (but i am not sure if i started to do this right way), i stucked on this problem for while:
changeIsOpenState(typeOfPart: Number, id: Number) {
console.log(this.state.data.packages);
const selectedObject = this.state.data.packages.filter((obj) => {
const val= (obj.id === id) ? obj : false;
return val;
});
}
Array of data i want update (isOpen property).

what about immutably? I think u can use dot-prop-immutable package in this way:
const state = {
packages: [
{ isOpen: false, id: 1 },
{ isOpen: false, id: 2 },
{ isOpen: false, id: 3 }
]
};
const index = state.packages.findIndex(obj => obj.id === 3);
const newState = dotProp.set(state, `packages[${index}].isOpen`, true);

you could do it the ol' way :
changeIsOpenState(typeOfPart: Number, id: Number) {
// Copy the packages so you won't mumtate your state directly
const packages = Object.assign({}, ...this.StaticRange.data.packages);
// Get the package to edit and its index in the packages object
let packageIndex;
let packageToEdit;
for(let i = 0; i <= packages.length; i++){
if(packages[i].id === id){
packageIndex = i;
packageToEdit = packages[i];
packageToEdit.isOpen = true
}
}
packages[packageIndex] = packageToEdit;
setState({...this.state, data:{...this.state.data, packages}});
}

I did it like this:
1.I copy current data to another variable
2.Filter data by id
3.Save array key of item with same id
4. Change cloned value with negation
5. save new data to state
changeIsOpenState(typeOfPart: Number, id: Number) {
const subjectDataCopy = cloneDeep(this.state.data);
const keys = [];
subjectDataCopy.packages.filter((obj, key) => {
if (obj.id === id) {
keys.push(key);
}
});
subjectDataCopy.packages[keys[0]].isOpen = !subjectDataCopy.packages[keys[0]].isOpen;
this.setState({data: subjectDataCopy});
}
If there is better option to do this please let me know :)

Related

React "magically" updates two states instead of one

I have two states defined like so:
const [productProperties, setProductProperties] = useState<
PropertyGroup[] | null
>(null);
const [originalProductProperties, setOriginalProductProperties] = useState<
PropertyGroup[] | null
>(null);
The first one is supposed to be updated through user input and the second one is used later for a comparison so that only the PropertyGroup's that have changed values will be submitted via API to be updated.
I have done this a thousand times before, but for some reason when I change the name value for a PropertyGroup and update the state for 'productProperties' like so:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i].name = {
...update[i].name,
[selectedLocale]: e.currentTarget.value,
};
setProductProperties([...update]);
}
}
The state of originalProductProperties also updates. Why? setOriginalProductProperties is never called here, I am also not mutating any state directly and I use the spread operator to be sure to create new references. I am lost.
Preface: It sounds like the two arrays are sharing the same objects. That's fine provided you handle updates correctly.
Although you're copying the array, you're modifying the object in the array directly. That's breaking the main rule of state: Do Not Modify State Directly
Instead, make a copy of the object as well:
(e, itemId) => {
const update = [...productProperties];
const i = update.findIndex((group) => group.id === itemId);
if (i !== -1) {
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update); // (No need to *re*copy the array here, you've already done it at the top of the function)
}
}
Or, since you have that i !== -1 check there, we could copy the array later so we don't copy it if we don't find the group matching itemId:
(e, itemId) => {
const i = productProperties.findIndex((group) => group.id === itemId);
if (i !== -1) {
const update = [...productProperties];
update[i] = { // *** Note making a new object
...update[i],
[selectedLocale]: e.currentTarget.value,
};;
setProductProperties(update);
}
}
FWIW, in cases where you know there will be a match, map is good for this (but probably not in this case, since you seem to indicate the group may not be there):
(e, itemId) => {
const update = productProperties.map((group) => {
if (group.id === itemId) {
// It's the one we want, create the replacement
group = {
...group,
[selectedLocale]: e.currentTarget.value,
};
}
return group;
});
setProductProperties(update);
}
Or sometimes you see it written with a conditional operator:
(e, itemId) => {
const update = productProperties.map((group) =>
group.id === itemId
? { // It's the one we want, create a replacement
...group,
[selectedLocale]: e.currentTarget.value,
}
: group
);
setProductProperties(update);
}

React: useState array doesn't change when state change method called

Array state doesn't change when state change method is beign called :
const [arrayOfDocuments, setArrayOfDocuments] = useState([]);
i tried : setArrayOfDocuments(...[]); or setArrayOfDocuments([]);
where i use my method :
const pushToArrayOfDocuments = (obj) => {
const arr = arrayOfDocuments;
if (obj.filename && obj.file && obj.expiredate && obj.doctype) {
const index = arr.map((e) => e.filename).indexOf(obj.filename);
if (index !== -1) {
arr[index] = obj;
} else {
arr.push(obj);
}
setArrayOfDocuments(arr);
}
};
Maybe the problem is push? and i should do setArrayOfDocuments(...arr); or setArrayOfDocuments(prev => [...prev,...arr]) but if doing so i guess it will go in infinte rendering as i'm passing pushToArrayOfDocuments to the subcomponents.
Like this :
OperatorDocument
key={`Durc${count}`}
title="Durc"
description="Descrizione Durc"
setDocument={pushToArrayOfDocuments}
document={getObjectByName('Durc')}
filedocname="Durc"
/>
edit :
doing like this : setArrayOfDocuments([...arr]);
i get Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
Any help is appreciated.
Firstly, you should never mutate useState's state directly, use them as immutable entities. If you want to use it as initial value, clone it before:
const arr = [...arrayOfDocuments]
// or
const arr = arrayOfDocuments.slice()
Secondly, you are passing the same state array to the setter, then the state will not be updated. Cloning the state will solve this second point.
Finally, the best way to construct a new state from the old value is using a function:
setState(oldValue => (/* construct new state based on old value */))
this will avoid using a value that is not up to date.
At the end, you will have:
const pushToArrayOfDocuments = (obj) => {
if (obj.filename && obj.file && obj.expiredate && obj.doctype) {
setArrayOfDocuments(oldArr => {
const arr = oldArr.slice();
const index = arr.map((e) => e.filename).indexOf(obj.filename);
if (index !== -1) {
arr[index] = obj;
} else {
arr.push(obj);
}
return arr;
}
)
}
};
You need to clone your array before adding it to state.
const arr = arrayOfDocuments.slice();
Full snippet:
const pushToArrayOfDocuments = (obj) => {
if (obj.filename && obj.file && obj.expiredate && obj.doctype) {
const arr = arrayOfDocuments.slice();
const index = arr.findIndex(({ filename }) => filename === obj.filename);
if (index > -1) {
arr[index] = obj;
} else {
arr.push(obj);
}
setArrayOfDocuments(arr);
}
};
I add a similar problem, and I solved by
instead of
const arr = arrayOfDocuments
try spreading the initial array
const arr = [...arrayOfDocuments]

Change Object Index

Is there way to change the index dynamically? or rebuild this object to where the 1 will be the Id of what ever object get passed into the function? Hope this makes sense.
export const createTree = (parentObj) => {
//keep in memory reference for later
const flatlist = { 1: parentObj }; <---- change the 1 based on parent.Id
...
}
My attempt thinking it would be easy as:
const flatlist = { parentObj.Id: parentObj };
Use computed property names to create a key from an expression:
const createTree = (parentObj) => {
const flatlist = { [parentObj.id]: parentObj };
return flatlist;
}
console.log(createTree({ id: 1 }));

Problem with setState ReactJS, done in todoList

Hello i have problem about change state after onClick with this function i dont know why this is doesnt work because console.log displayed difference value and i dont know why i cant set the same state.
`doneUndone = (index) => {
console.log(!this.state.scores[index].done)
const test = !this.state.scores[index].done
this.setState({
scores: test,
})
}`
here will be all code of this aplication https://codepen.io/RetupK/pen/xxKmELd?editors=0010
As per your state scores is an array and in your method of done you are assigning Boolean value to it where as it must be an array itself. Because you're using .map() in your render method which only works with array not boolean.
What you need to do is change the done property of particular object in scores and pass the newly updated scores object to setState method and it will work.
doneUndone = (index) => {
this.state.scores[index].done = !this.state.scores[index].done
this.setState({
scores: this.state.scores,
})
}
If you use this.state to get previously done value you might have problems when you fire doneUndone method multiple times (e.g. clicking button few times in a row). That's why I suggest such solution:
doneUndone = index => {
this.setState(state => ({
scores: state.scores.map((score, idx) =>
idx === index ? { ...score, done: !score.done } : score
)
}));
};
The doneUndone method isn't updating the state properly. You can check the method form here.
doneUndone = (index) => {
const score = this.state.scores[index];
const updatedScore = {...score, done: !score.done};
const updatedScores = [...this.state.scores];
updatedScores[index] = updatedScore;
this.setState({
...this.state,
scores: updatedScores
})
}
doneUndone = (index) => {
let modScores = this.state.scores;
modScores[index].done=!this.state.scores[index].done
this.setState({
scores: modScores
})
}
cleaner way to do it

Updating State React

The following image represents an object with two ui controls that are stored as this.state.controls
Initially the statesValue values are set via data that is received prior to componentDidMount and all is good. However updates to the each of the controls statesValues are sent via an event , which is handled with the following function
const handleValueStateChange = event => {
let controls = Object.entries(this.state.controls);
for (let cont of controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
}
}
}
};
which updates the values happily, however bearing in mind the updated values that change are a subset of this.state.controls, I have no idea how to use this.setState to update that that has been changed.
thanks in advance for any pointers
Instead of using Object.entries try destructuring to keep the reference to the objects.
And have a look at lodash. There are some nice helper functions to iterate over objects like mapValues and mapKeys. So you can keep the object structure and just replace the certain part. Afterwards update the whole state-object with the new one.
const handleValueStateChange = event => {
let {controls} = this.state;
controls = _.mapValues(controls, (cont) => {
const states = cont[1].states;
if (states) {
_.mapValues(states, (contUuid,stateId) => {
if (contUuid === event.uuid) {
cont[1].statesValue[stateId] = event.value;
}
});
}
return cont;
});
this.setState({controls});
};
Code is not tested but it should work like this.
Problem is you're updating an object which you've changed from it's original structure (by using Object.entries). You can still iterate in the same way however you'll need to update an object that maintains the original structure. Try this:
Make a copy of the controls object. Update that object. Replace it in state.
const handleValueStateChange = event => {
// copy controls object
const { controls } = this.state;
let _controls = Object.entries(controls);
for (let cont of _controls) {
let states = cont[1].states;
if (states) {
let state = Object.entries(states);
for (let [stateId, contUuid] of state) {
if (contUuid === event.uuid) {
// update controls object
controls[cont[0]].statesValue[stateId] = event.value;
}
}
}
}
}
// replace object in state
this.setState({controls});
};

Categories