I'm trying to push an object to the state.
I created a function for this. This function should copy the first state object and add it to the end of the state.
addField() {
const index = (this.state?.fields.length) -1
const newFields = [...this.state?.fields[index]]
console.log("newFields", newFields) // [ { "key": "input_field_name", "value": "This is a value 4" }, { "key": "field_name", "value": "field name's value 4" }, { "key": "datatype", "value": "text" }, { "key": "Datatype", "value": "Label 4" }]
this.setState(
{
fields: [[...this.state.fields[index]], newFields],
},
() => {
console.log("updated state", this.state);
}
);
console.log("state: ", this.state)
}
This is my state:
fields: IFieldDefinition[][];
activeFields: {
key: number;
fields: IFieldDefinition[];
};
Updated code:
Now, when I run the function, it creates a new state from the last object of the state instead of add.
how can I fix that?
fields is an array, so you cannot assign values as an object with {}
If you want to add that new state to the end of the state, you can use the below approach
const index = (this.state?.fields.length) - 1
const newFields = [...this.state?.fields[index]];
//if the current index is matched with which field you want to update
this.setState(
{
fields: this.state.fields.map((currentValue, currentIndex) =>
currentIndex === index ? [...currentValue, ...newFields] : currentValue
),
},
() => {
console.log("updated state", this.state);
}
);
console.log("state: ", this.state);
Do you want to remove the 0th index data and move it to the end of the array
if so
let a={A:[{a:1},{b:2},{c:3}]}
let temp=a.A.shift()
a.A.push(temp)
setState({...a})
Related
I'd like to update a react state (the "app" variable) object dynamically. The state has several properties, one of them is a nested object ("app.error" object). There is a kind of setter function ("setAppData") which takes any number of [existing key]: new value pairs of the state as parameter, create a new object and it should update the state according to "temp" object.
It works as intended when I want to update "app.error" using hard-coded variable or any other "app" properties, when I try to update the "app.error" using my setter function, it does not update.
const initialAppData: IAppData = {
error: {} as IFrontEndError,
interfaceTheme: UI_THEME.LIGHT,
};
function AppProvider(props: any) {
const [app, setApp] = useState<IAppData>(initialAppData);
useMemo(() => {
window.addEventListener("error", (event) => {
errorHandler({
colno: event.colno,
filename: event.filename,
lineno: event.lineno,
message: event.message,
});
});
}, []);
const errorHandler = (event: IFrontEndError) => setAppData({error: { ...event }});
const setAppData = (newValue: TUpdateAppData) => {
let key: keyof IAppData;
let temp = getAppData();
for (key in newValue) {
if (
!Object.prototype.hasOwnProperty.call(app, key) ||
!Object.prototype.hasOwnProperty.call(newValue, key)
)
continue;
temp = {
...temp,
[key]: newValue[key],
};
}
setApp({ ...temp, error: { ...temp.error } });
};
const getAppData = () => ({ ...app });
}
Using static update (it works as intended):
const setAppData = () => {
let temp = getAppData();
temp.error = {
colno: 1,
lineno: 1,
message: "asd",
filename: "asd"
}
setApp({ ...temp, error: { ...temp.error } });
};
The structure of the "temp" objects before passing the spreaded copy to the state setter are exactly the same. "temp" object before spreading, triggered by event:
{
"error": {
"colno": 11,
"filename": "http://127.0.0.1:5173/src/components/Debug.tsx?t=1667134926865",
"lineno": 29,
"message": "Error: asd"
},
"interfaceTheme": "light-theme"
}
getAppData() after error event:
{
"error": {},
"interfaceTheme": "light-theme"
}
"temp" object before spreading using hard-coded value:
{
"error": {
"colno": 1,
"lineno": 1,
"message": "asd",
"filename": "asd"
},
"interfaceTheme": "light-theme"
}
getAppData() after execution using hard-coded value:
{
"error": {
"colno": 1,
"lineno": 1,
"message": "asd",
"filename": "asd"
},
"interfaceTheme": "light-theme"
}
What is that I don't notice?
edit: sources of the project:
https://github.com/gazsop/magus_shared
https://github.com/gazsop/magus_react
I'm using an object in two different states, in on of the states I just set the object in the state, and the other state I change one of the values in the object before setting state, but when I make the change both of the states change.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = [];
return [...prev, i];
});
});
};
both states return an empty favmovies array
[
{
"id": "1",
"name": "development",
"image": "img.png",
"favmovies": []
},
{
"id": "2",
"name": "socialmedia",
"image": "img2.png",
"favmovies": []
},
{
"id": "3",
"name": "writing",
"image": "img2.png",
"favmovies": []
}
]
Issue
You are still mutating an object reference:
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
categoryList1["movies"].forEach((i) => {
setMovies((prev) => {
i["favmovies"] = []; // <-- mutation is here
return [...prev, i];
});
});
};
i["favmovies"] = []; mutates the i object that is still a reference in the categoryList1.movies array.
Solution
From what I can tell, you want to store categoryList1.movies array into the allCategory state, and then a copy of categoryList1.movies array with the favmovies array emptied/reset to an empty array.
Instead of for-each iterating over the categoryList1.movies array and enqueueing multiple state updates, just map categoryList1.movies array to a new array reference for the movies state.
const fetchCategories = () => {
setAllCategory(categoryList1["movies"]);
setMovies(movies => movies.concat( // <-- add to existing state
categoryList1.movies.map(movie => ({ // <-- map to new array
...movie, // <-- shallow copy movie object
favmovies: [], // <-- update property
}))
));
};
I have a nested array within the individual items in a collection.
{
"id": "RpFRcKLIgELlBLgIOJM4",
"Category": "",
"Method": "",
"User": "rWFZhAKk9eOSIIFoP0DqqvrC6WJ3",
"Foods": [
{
"Weight": 1.065,
"label": "Milk - Long Life (1 Litre) (1.065)",
"value": "Milk-LongLife(1Litre)"
},
{
"label": "Blueberries (0.125)",
"value": "Blueberries",
"Weight": 0.125
}
],
"Name": "456",
"Serves": ""
}
{
"id": "WQ6KBLevFsCdV73j4KU4",
"Category": "",
"Name": "123",
"Foods": [
{
"value": "Wine-White",
"label": "Wine - White"
},
{
"value": "Milk-LongLife(1Litre)",
"label": "Milk - Long Life (1 Litre)"
}
],
"Serves": "",
"User": "rWFZhAKk9eOSIIFoP0DqqvrC6WJ3",
"Method": ""
}
const useItemsMeals = () => {
const user = firebase.auth().currentUser;
const user1 = user.uid;
const [items, setItems] = useState([]); //useState() hook, sets initial state to an empty array
useEffect(() => {
const unsubscribe = firebase
.firestore()
.collection("meals")
.where("User", "==", user1)
.orderBy("Category", "asc")
.onSnapshot(snapshot => {
const listItemsMeals = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data()
}));
setItems(listItemsMeals);
console.log(listItemsMeals);
});
return () => unsubscribe();
}, []);
return items;
};
I am having a tough time trying to display items from the 'Foods' array, am currently using for my return:
const listItemMeals = useItemsMeals();
{listItemMeals.map(item => (
<TableRow hover key={item.id} id={item.id}>
<TableCell>{item.Category}</TableCell>
<TableCell>{item.Name}</TableCell>
<TableCell>{item.Foods}</TableCell>
When doing this it tells me:
Error: Objects are not valid as a React child (found: object with keys {label, value}). If you meant to render a collection of children, use an array instead.
I think I need to map this nested array again somehow - but for the life of me - cannot figure it out!
You're almost there.
Your useItemsMeals functions loads the data from Firestore, and sets it correctly into the items variable in the state to render it. But then you use const listItemMeals = useItemsMeals() in your rendering code, which messes things up.
You should not try to return any value from useItemsMeals, and instead solely rely on the items variable from the state to pass the information between the database and the rendered.
So:
// return items; 👈 remove this
---
// const listItemMeals = useItemsMeals(); 👈 remove this
---
{items.map(item => ( // 👈 read items from the state instead
You need to loop over the Foods array again. Like this
const listItemMeals = useItemsMeals();
{listItemMeals.map(item => (
<TableRow hover key={item.id} id={item.id}>
<TableCell>{item.Category}</TableCell>
<TableCell>{item.Name}</TableCell>
{
item.Foods.map(food=>(
<div> //this can div or a new row tag
<TableCell>{food.weight}</TableCell>
<TableCell>{food.label}</TableCell>
<TableCell>{food.value}</TableCell>
</div>))
}
I want to update the state using react Hooks useState(); ?
Here is an example :
I have global state on top of the app:
const [familyTree, setFamilyTree] = useState([
{
fam_id: 1,
name: "No name",
attributes: {
"Key1": "*",
"Key2": "*",
},
children: [
{
fam_id: 2,
name: "No Name2",
attributes: {
"Key1": "*",
"Key2": "*",
},
},
],
},
]);
I have a current object to update the global state:
let res = {
fam_id: 2,
name: "No Name2",
attributes: {
"Key1": "Update this",
"Key2": "*",
},
},
Recursive function in this case helps me to update global state with matched ID, but I have problem now,
const matchAndUpdate = (updater, target) => {
if (updater.fam_id === target.fam_id) {
target.name = updater.name;
target.attributes = updater.attributes;
}
if ("children" in target && Array.isArray(target.children)) {
target.children.forEach((child) => {
matchAndUpdate(updater, child);
});
}
};
familyTree.forEach((g) => {
matchAndUpdate(res, g);
setFamilyTree({ ...g }); // here is my try, this works on start, but on secound update i got error about forEach is not a function...
});
I don't know where to update state on correct way?
Thanks, o/
Because you update state inside of forEach().
Maybe you should use .map and update state then at the end of check array.
This is the solution:
const matchAndUpdate = (updater, children) => {
return children.map(_child => {
if (updater.fam_id === _child.fam_id) {
return {
...updater,
children: _child.children && Array.isArray(_child.children) ? matchAndUpdate(updater, _child.children) : null
};
} else {
return {..._child,children: _child.children && Array.isArray(_child.children) ? matchAndUpdate(updater,_child.children) : null};
}
});
};
This will return and array of children, so you will begin from the initial array:
const finalFamily = matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
finalFamily will be the final updated array.
You can update the state like this:
// Option 1:
setFamilyTree(matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
// Option 2:
const newFamilyTree = matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
setFamilyTree(newFamily);
--- NEXT QUESTION-- -
I understand that you want to create a method to push new children to child specified by id.
I developed a method that maintains attributes and old children:
const addChildrenToChild = (parent,numChildren) => {
const arrayChildren = [];
for (let i = 0; i < numChildren; i++) {
arrayChildren.push({
fam_id: Math.floor(Math.random() * 100),
name: "No name",
attributes: {
key1:"",
key2:""
},
});
}
return {...parent,children:parent.children && Array.isArray(parent.children) ? parent.children.concat(arrayChildren) : arrayChildren }
}
And upgrade matchAndUpdate to maintains old children
const matchAndUpdate = (updater, children) => {
return children.map(_child => {
if (updater.fam_id === _child.fam_id) {
return {
...updater,
children: updater.children
//Filter updater children
.filter(_childFiltered =>
_child.children && Array.isArray(_child.children) ?
//check if exists new child in old children
_child.children.some(
_childToCheck => _childToCheck.fam_id !== _childFiltered.fam_id
) : true
)
//concat old children and check to update
.concat(
_child.children && Array.isArray(_child.children)
? matchAndUpdate(updater, _child.children)
: []
)
};
} else {
return {
..._child,
children:
_child.children && Array.isArray(_child.children)
? matchAndUpdate(updater, _child.children)
: []
};
}
});
};
And now. You can use the other method at the same time to add new children:
// Now we are going to add new children to the first element in familyTree array, and maintains old children if it has.
const newFamilyTree = matchAndUpdate(
addChildrenToChild(familyTree[0], 10),
familyTree
);
setFamilyTree(newFamilyTree);
I have an array like below
[
1:false,
9:false,
15:false,
19:false,
20:true,
21:true
]
on click i have to change the value of specific index in an array.
To update value code is below.
OpenDropDown(num){
var tempToggle;
if ( this.state.isOpen[num] === false) {
tempToggle = true;
} else {
tempToggle = false;
}
const isOpenTemp = {...this.state.isOpen};
isOpenTemp[num] = tempToggle;
this.setState({isOpen:isOpenTemp}, function(){
console.log(this.state.isOpen);
});
}
but when i console an array it still shows old value, i have tried many cases but unable to debug.
This is working solution,
import React, { Component } from "react";
class Stack extends Component {
state = {
arr: [
{ id: "1", value: false },
{ id: "2", value: false },
{ id: "9", value: false },
{ id: "20", value: true },
{ id: "21", value: true }
]
};
OpenDropDown = event => {
let num = event.target.value;
const isOpenTemp = [...this.state.arr];
isOpenTemp.map(item => {
if (item.id === num) item.value = !item.value;
});
console.log(isOpenTemp);
this.setState({ arr: isOpenTemp });
};
render() {
let arr = this.state.arr;
return (
<React.Fragment>
<select onChange={this.OpenDropDown}>
{arr.map(item => (
<option value={item.id}>{item.id}</option>
))}
</select>
</React.Fragment>
);
}
}
export default Stack;
i hope it helps!
The problem is your array has several empty value. And functions like map, forEach will not loop through these items, then the index will not right.
You should format the isOpen before setState. Remove the empty value
const formattedIsOpen = this.state.isOpen.filter(e => e)
this.setState({isOpen: formattedIsOpen})
Or use Spread_syntax if you want to render all the empty item
[...this.state.isOpen].map(e => <div>{Your code here}</div>)