how to setState an object of an object in react - arrays

I'm hoping someone can give me some syntax/explanation help here i'm trying to call setState on an object nested in an object (data) in my state i'm a little stumped?
I'm not sure how to actually push my object onto the specified array in the setState function?
Can someone help me out? Many thanks!
here is the state i'm working with:
state={
height: 50,
parentCount: 1,
data:
{
parentId: 0,
name: 'parent',
children: [{name: 'Child One', distToWater: 0, children: [] }, {name: 'Child Two', distToWater: 0, children: [] }]
},
}
Here's my function where I try to add a child to my children [] array that's nested inside my data object in state:
addChild = () =>{
for (let x in data.children ){
for (child in x){
let closest = 99999
if(child.distToWater < closest){
closest = child.distToWater
var newBest = child
let newChild = {
name: 'child',
distToWater: closest - 1,
children: []
}
}
this.setState({data.children[newBest]: [...newChild] }) //use setState to add a child object
}
}
}

Since it is nested deep inside, something like the following code snippet should do.
const kid = { name: 'John', distToWater: 0, children: [] } // new object to add
const newChildren = [...this.state.data.children, kid]
const newData = { ...this.state.data, children: newChildren }
const newState = { ...this.state, data: newData }
this.setState(newState)
The above code snippet uses the spread operator. In case if you have not seen it before, it is a new JavaScript operator which would come very handy. (MDN link)
I am not sure why you had added the comment //use setState to add a child when the code snippet does not use hooks. I think hooks, or any other state management tool would be beneficial if the state object is so deeply nested.

You can try something like this
this.setState(prevState => ({
...prevState,
data: {
...prevState.data,
children: [...prevState.data.children, newBest]
}
}))

Related

In my React application, how can I make sure that my "useState" state is up-to-date before modifying it further?

In my React application, I have an array of objects implemented using a "useState" hook. The idea is to update an existing object within this array if an object with a specific "id" already exists or to add a new object if it does not.
The problem that I am encountering is that my state is not up-to-date before I am modifying it (= updating and/or adding objects). Therefore, the check whether an object with a certain "id" already exists fails.
Here is a (very much simplified) example demonstrating the problem:
import React, { useEffect, useState } from 'react';
interface Dummy {
id: string;
value: number;
}
const TestComponent = () => {
const [dummies, setDummies] = useState<Dummy[]>([]);
const addDummy = (id: string, value: number) => {
const dummyWithIdAlreadyUsed = dummies.find(
(dummy: Dummy) => dummy.id === id
);
if (!dummyWithIdAlreadyUsed) {
setDummies((dummies) => [...dummies, { id: id, value: value }]);
}
};
const test = () => {
addDummy('dummy1', 1);
addDummy('dummy1', 2);
addDummy('dummy2', 3);
addDummy('dummy2', 4);
addDummy('dummy3', 5);
addDummy('dummy4', 6);
addDummy('dummy4', 7);
};
useEffect(() => {
console.log(dummies);
}, [dummies]);
return <button onClick={test}>Test!</button>;
};
export default TestComponent;
The desired output is:
0: Object { id: "dummy1", value: 1 }
1: Object { id: "dummy2", value: 3 }
2: Object { id: "dummy3", value: 5 }
3: Object { id: "dummy4", value: 6 }
The actual output is:
0: Object { id: "dummy1", value: 1 }
1: Object { id: "dummy1", value: 2 }
2: Object { id: "dummy2", value: 3 }
3: Object { id: "dummy2", value: 4 }
4: Object { id: "dummy3", value: 5 }
5: Object { id: "dummy4", value: 6 }
6: Object { id: "dummy4", value: 7 }
How can I make sure that my state is up-to-date before executing setDummies?
State updates are asynchronous and queued/batched. What that means here specifically is that the dummies variable (the one declared at the component level anyway) won't have an updated state until after all 7 of these operations have completed. So that if condition is only ever checking the initial value of dummies, not the ongoing updates.
But you do have access to the ongoing updates within the callback here:
setDummies((dummies) => [...dummies, { id: id, value: value }]);
So you can expand that callback to perform the logic you're looking for:
const addDummy = (id: string, value: number) => {
setDummies((dummies) => {
const dummyWithIdAlreadyUsed = dummies.find(
(dummy: Dummy) => dummy.id === id
);
if (!dummyWithIdAlreadyUsed) {
return [...dummies, { id: id, value: value }];
} else {
return dummies;
}
});
};
In this case within the state setter callback itself you're determining if the most up-to-date version of state (as part of the queue of batched state updates) contains the value you're looking for. If it doesn't, return the new state with the new element added. If it does, return the current state unmodified.
This is a problem of closure, you can fix it by using an arrow function in the setter, check this article :
https://typeofnan.dev/why-you-cant-setstate-multiple-times-in-a-row/
Maybe you can refactor your code to avoid calling setters many times in a row by doing javascript operation before calling the setter
for exemple :
const test = (data: Dummy[]) => {
// to remove duplicate and keep the latest
const arr = data.reverse()
const latestData = arr.filter(
(item, index) =>
arr.indexOf(item) === index
);
setDummies([...dummies, ...latestDummiesData])
};

Redux-Toolkit: Mutation based on current state leaves inconsistent state, am I using it wrong? (changeNodeParent-operation)

I'm trying to move a 'node' in redux-toolkit to a different parent.
It often works, but sometimes it leaves an inconsistent state.
My State looks like this: (I need deeply nested nodes, I am storing them like this to keep the state flat, is this ok?)
{
today: { id: "today", parentId: "0", text: "Parent of #1, Child of #0", childIds: ["1"] },
1: { id: "1", parentId: "today", text: "Parent of none, Child of today", childIds: [] },
}
So nodes have a node.parentId and node.childIds.
Here is my changeParentNode reducer:
changeParentNode: (state, { payload: { id, newParentId } }) => {
console.log("trying changeParentNode with:", state[id]);
const oldParentId = state[id].parentId;
console.log("oldParentId", oldParentId);
const oldIndex = state[oldParentId].childIds.indexOf(oldParentId);
console.log("oldIndex", oldIndex);
const [removed] = state[oldParentId].childIds.splice(oldIndex, 1);
if (removed.length === 0) console.log("Error: changeParentNode didn't remove an element from the parent's array");
console.log("removed", removed);
state[id].parentId = newParentId;
state[newParentId].childIds.push(id);
},
Problem: in the attached screenshot of redux-devtools, it removes id #8 from the source today.childIds array when it should have removed id#4.
I guess am using redux-toolkit wrong, is it ok to query the state like
const oldIndex = state[oldParentId].childIds.indexOf(oldParentId)?
Is this a race-conditions issue?

Setting Nested Array in React Context

I am trying to figure out how to update an array that is nested within a React Context. Below is the context I am working with. It consists of "lists". Each list contains an array of "items".
import React, {useState, createContext} from 'react';
export const ListerContext = createContext();
export const ListerProvider = (props) => {
const [lists, setLists] = useState([
{
id: 1,
items: [{
itemid: 1,
text: 'Hello'
},
{
itemid: 2,
text: 'world'
}]
},
{
id: 2,
items: [{
itemid: 2,
text: 'Test'
}]
}
]);
return(
<ListerContext.Provider value={[lists, setLists]}>
{ props.children }
</ListerContext.Provider>
);
}
I have been trying to change the nested arrays using the "setLists" method below but it is not working. What am I doing wrong here?
const removeListItem = (e) => {
setLists((prevList)=>{
for(var i = 0; i < prevList.length; i++){
if(prevList[i].id === parseInt(e.target.value[2])){
prevList[i].items = (prevList[i].items.filter(function(item) {
return item.itemid !== parseInt(e.target.value[0]);
}))
}
}
return prevList;
});
}
As #zero298 mentioned in their comment, you need to pass the entire list into your state update function setLists. You are initializing the state and its update function with a "list of dictionaries", so you need to keep treating it as such, you can't selectively change few properties inside the dictionary or few elements in the list without having to update the entire list.
Easy correction of your code will be to conditionally make a new copy of your list with updated values, and then setting that list as your new state using your state update function.
Cheerio!

how to map to update an objects with setState in Reactjs

I have this state:
state = {
formdata:{
name: null,
about: null,
price: null,
offerPrice:null,
playStoreUrl:null,
appStoreUrl:null ,
photo:null,
}
}
what I want: update form inside modal i used it to update products. I used new props inside componentWillReceiveProps
I did:
componentWillReceiveProps(nextProps){
let Updateproduct = nextProps.productlist.productlist.Products;
Updateproduct.map((item,i) => {
let formdata = Object.assign({}, this.state.formdata);
formdata.name = item.name
formdata.about = item.about
formdata.price = item.price
formdata.offerPrice = item.offerPrice
formdata.playStoreUrl = item.playStoreUrl
formdata.appStoreUrl = item.appStoreUrl
formdata.photo = item.photo
console.log(formdata)
this.setState({formdata})
})
}
MyProblem: this filled the objects but in the form inside modal only I saw the last product not all in modal when click to update any product it. Note:Updateproduct contains:
{
about: "about product1"
appStoreUrl: "https://itunes.apple.com/us/app/snapchat/id447188370?mt=8"
name: "p1"
offerPrice: 99.99
photo: "images/products/"
playStoreUrl: "images/products/"
price: 1000
}
{
about: "about product2"
appStoreUrl: "https://itunes.apple.com/us/app/snapchat/id447188370?mt=8"
name: "p2"
offerPrice: 99.99
photo: "images/products/"
playStoreUrl: "images/products/"
price: 2000
}
Issue is, you want to store single specific product item clicked by user in state variable, but with current code you are always storing the last product item. Also setState in loop is not a good way.
To solve the issue, store the clicked product detail in state inside onClick handler function only, instead of componentWillReceiveProps method. For that you need to bind the product id with onClick function.
Like this:
onClick={this.getProductId.bind(this, item.id)}
// click handler function
getProductId = (id) => {
let productObj = {};
let Updateproduct = this.props.productlist.productlist.Products;
Updateproduct.forEach((item,i) => {
if(item.id == id) {
productObj = {
name: item.name,
about: item.about,
price: item.price,
offerPrice: item.offerPrice,
playStoreUrl: item.playStoreUrl,
appStoreUrl: item.appStoreUrl,
photo: item.photo
};
this.setState({ formdata: productObj, id: id })
}
})
}
You’re setting your state inside the map. If you think of the map as a loop that means you’re overriding it each iteration. So you will only ever have the last one displaying.
I think that's because your setState() call is in the wrong place (and only one object at a time):
let data =[];
UpdateProduct.map((item, i) => {
let formdata = null;
// do your formdata stuff but append it as part of an array
data.push(formdata);
});
this.setState({ formdata: data });
Then I think it should get all the products.

React JS - How to set state of variable inside variable?

I want to set state of this form :
this.state = {
filter: {
search: null,
brands: null,
price: null
}
}
How to set value for search / brands / price ?
Do the following:
this.setState({
filter: {
search: 'value',
brands: 'value',
price: 'value'
}
})
The key is that you don't want to ever mutate a value in state. As a result, you must copy the filter object before passing it to setState. Example:
onSearchChange(value) {
this.setState((state) => {
return {
filter: {
...state.filter,
search: value
}
})
}
Note that I am passing a function to setState. Since the next value of state relies on the previous value, you want to use an updater functions, as the setState docs recommend.
In general, it is nicer if you can keep your state flat. So rather than having a filter object in state, your shape could just be
this.state = {
search: null,
brands: null,
price: null,
}
In which case the above onSearchChange function would just be
onSearchChange(value) {
this.setState({search: value})
}
Definitely a lot easier on the eyes.
I recommend avoiding nested objects and keeping your state flat. e.g.
this.state = {
brandsFilter: null,
priceFilter: null,
searchFilter: null,
};
Component state in react is really nothing more than simple key-value pairs; that's what setState supports. Sure you can have nested objects if you really want. But as the other answers demonstrate, supporting such an approach can be needlessly complex.
you should use the setState function, you can set the filter with updated data like so
const newFilter = {...};
this.setState({filter: newFilter});
You should avoid to mutate React state directly, there are some functions can do immutable jobs for you (ex Object.assign):
const currentFilter = this.state.filter;
const newFilter = Object.assign({}, currentFilter, {search: "new search", brands: [], price: 100});
this.setState({filter: newFilter});
ES 6:
const currentFilter = this.state.filter;
this.setState({
filter: {
...currentFilter,
search: "new search",
brands: []
}
});
this.setState({
filter: {
...this.state.filter,
search: "new search",
brands: 'value',
price: 'value'
}
});
You may try this with Spread Operator.
If you need to preserve the previous filter value.
other wise you can,
this.setState({
filter: {
search: "new search",
brands: 'value',
price: 'value'
}
});
let newfilter = Object.assign({}, this.state.filter)
newfilter.search ="value";
newfilter.brands ="value";
newfilter.price ="value";
this.setState({
filter:newfilter
})
You can access the search, brands, price by using:
this.setState({
filter.search = true,
filter.brands = 'stackoverflow',
filter.price = 1400
})
and to access it, just like usual state access (this.state.filter.search).

Resources