React setstate nested object issue - javascript

I am not able to solve this setState second night and I'm already desperate. I have heavily nested object which I'm trying to update. In case I have multiple elements in todaysMenu and I'm trying to update state for second element whole array gets "stored" in first element of todaysMenu.
onChangeAnyValue(values, itemIndex) {
const key = Object.keys(values.x)[0];
const provideDate = values.date;
this.setState(prevState => ({
data: prevState.data.map(day => day.date === provideDate ? {
...day,
todaysMenu: [{
...day.todaysMenu,
[itemIndex]: {
...day.todaysMenu[itemIndex],
dish: {
...day.todaysMenu[itemIndex].dish,
[key]: values.x[key]
}
}
}]
} : day)
}));
}
In case I remove square brackets its stored as just objects.
Thank you for your time!

You'll want to change:
todaysMenu: [{
...day.todaysMenu,
[itemIndex]: {
...day.todaysMenu[itemIndex],
dish: {
...day.todaysMenu[itemIndex].dish,
[key]: values.x[key]
}
}
}]
...to:
todaysMenu: day.todaysMenu.map((item, index) =>
index === itemIndex
? { ...item, dish: { ...item.dish, [key]: values.x[key] } }
: item
)
What you currently have is creating an Array with one object instead of converting an Array to a modified Array.

Related

How to change update an object within a useState array

Suppose we have an array of objects in userInformation:
[
{
firstName:'Bob',
lastName:'Dude',
},
{
firstName:'John',
lastName:'Rad',
}
]
const [userInformation, setUserInformation] = useState([]);
userInformation.forEach((user, index) => {
if (snapshot.val().leadID === user.leadID) {
setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
});
I would like to update the second object.
My code doesn't seem to be working quite right. Any suggestions?
Yes few suggestions I have for you:)
First of all you have never assigned userinformation to your state.
So it should be some what like below
const [userInformation, setUserInformation] = useState(userinformation);
Or you must be getting userinformation from an API and using useEffect to initialize it.
Second thing is you must have an id as well as index key on each user something like this:
[
{
leadId:1,
firstName:'Bob',
lastName:'Dude',
index:1
},
{
leadId:2,
firstName:'John',
lastName:'Rad',
index:2
}
]
Now, Coming to what you are expecting you can use map() function in such as way that once the condition is met, you update that particular user, otherwise you should return back same users when condition is not met.
const updatedUsers = userInformation.map((user, index) => {
if (snapshot.val().leadID === user.leadID) {
return setUserInformation((userInformation) => ({
...userInformation,
[index]: snapshot.val(),
}));
}
return userInformation;
});
Here I think a simple find() would do the trick, rather than trying to loop the array.
const updateInfo = (id, obj /* snapshot.val() */) => {
const item = userInformation.find(({ leadID }) => leadID === id);
const updatedItem = {
...item,
...obj
};
setUserInformation((previousInfo) => {
...userInformation,
...updatedItem
})
}
Sorry for the lack of information provided in my question.
I was using firebase realtime with React, and not wrapping my logic in a useEffect was one problem.
I went with this solution:
setUserInformation(
userInformation.map((user) =>
user.leadID === snapshot.val().leadID
? {
...user,
...snapshot.val(),
}
: user
)
);
Thank you for all your answers!

Remove an Object from Array which is nested in an Object

I started a little bit playing with redux and i am amazed so far.
My problem right now is, that my new reducer function changes the type of one state variable and i dont want that.
The state shall have a form like that:
I only want to delete an object from a jsons array:
pseudo:
delete state.items[item_index].jsons[json_to_delete_index]
I ended up with this reducer, which is returning the item state now as an object and not as an array.
case DELETE_JSON:
const item_index = state.items.findIndex((url) => url.url_id === action.payload.url_id);
const json_index = state.items[item_index].jsons.findIndex((json) => json.json_id === action.payload.json_id);
return {
...state,
items: {
...state.items,
[item_index]: {
...state.items[item_index],
jsons:
[
...state.items[item_index].jsons.splice(0, json_index),
...state.items[item_index].jsons.splice(json_index + 1)
]
}
}
};
I tried various approaches so far, but changing states inside highly nested objects seems still like a torture with redux. Does anybody maybe know a way to write it?
Changing state with highly nested objects can be difficult but map and filter functions are really helpful in this case
const item_index = state.items.findIndex((url) => url.url_id === action.payload.url_id);
const json_index = state.items[item_index].jsons.findIndex((json) => json.json_id === action.payload.json_id);
return {
...state,
items: state.items.map((item, index) => (index === item_index ?
{ ...item, item.jsons.filter((json, i) => (i !== json_index)) } : item))
};
I solved it by using update() from immutability-helpers.
Very handy
import update from 'immutability-helper';
/* some other code */
case DELETE_JSON:
const item_index = state.items.findIndex((url) => url.url_id === action.payload.url_id);
const json_index = state.items[item_index].jsons.findIndex((json) => json.json_id === action.payload.json_id);
return update(state, {
items: {
[item_index]: {
jsons: {$splice: [[json_index]]}
}
}
});

How to add obj to to state in my example Redux React

I have a problem with adding obj to my arr in redux store.
I want to check if some element in my arr have the same id with payload id - I don't want to push, if not push the object to array.
The initial state of the array - [] (empty)
MY REDUCER CODE:
case "UNSHIFT_COUNTRY": {
console.log("P", payload);
return {
...state,
selectedCountries: [
...state.selectedCountries,
state.selectedCountries.forEach(item => {
if (item.id !== payload.id) {
state.selectedCountries.unshift(payload);
}
})
]
};
}
PAYLOAD IS AN OBJECT
Thanks for answers!
Use find to check if your array has an object with the same id:
case "UNSHIFT_COUNTRY": {
const found = state.selectedCountries.find(country => country.id === payload.id);
return found ? state : {
...state,
selectedCountries: [...state.selectedCountries, payload]
};
}

Which approach in React is better?

Below both code does exactly same but in different way. There is an onChange event listener on an input component. In first approach I am shallow cloning the items from state then doing changes over it and once changes are done I am updating the items with clonedItems with changed property.
In second approach I didn't cloned and simply did changes on state items and then updated the state accordingly. Since directly (without setState) changing property of state doesn't call updating lifecycles in react, I feel second way is better as I am saving some overhead on cloning.
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const clonedItems = Array.from(items);
clonedItems.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items: clonedItems });
};
OR
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
ele.rate = Number(value);
}
});
this.setState({ items });
};
You can use this
this.setState(state => {
const list = state.list.map(item => item + 1);
return {
list,
};
});
if you need more info about using arrays on states, please read this: How to manage React State with Arrays
Modifying the input is generally a bad practice, however cloning in the first example is a bit of an overkill. You don't really need to clone the array to achieve immutability, how about something like that:
handleRateChange = (evnt: React.ChangeEvent<HTMLInputElement>) => {
const {
dataset: { type },
value,
} = evnt.target;
const { items } = this.state;
const processedItems = items.map((ele: NetworkItem) => {
if (ele.nicType === type) {
return {
...ele,
rate: Number(value)
};
} else {
return ele;
}
});
this.setState({ items: processedItems });
};
It can be refactored of course, I left it like this to better illustrate the idea. Which is, instead of cloning the items before mapping, or modifying its content, you can return a new object from the map's callback and assign the result to a new variable.

nested filter array method

The purpose of this code is to filter items by color. The filtered is value is not updating to the items that match the color. The filterData function is suppose to filter through the products and then filter through the colors in the array and return the products that are of that color
state = {
filtered:this.props.products,
color:[],
size:'all',
price:'all',
type:'all'
}
change = (e) => {
let value = e.target.value;
let name = e.target.name;
this.setState({
[name]:[...this.state.color,value]
}, () => {
this.filterData();
})
}
filterData = () => {
if (this.state.color) {
var newData = this.props.products.filter(product => {
return this.state.color.filter(c => {
return c === product.colors
})
})
}
this.setState({
filtered:newData
})
}
render() {
let list = this.state.filtered.map(product => {
return(
<div className="product" key={product.product_id}>
<Link to = {{ pathname: '/'+product.product_id}}>
<img className="product_image"src={product.image[0]} />
</Link>
<div className="product_info">
<p className="product_title">{product.title}</p>
<p className="product_price">${product.price}.00</p>
</div>
</div>
)
})
Try changing your filter function, like this:
filterData = () => {
var newData = this.propts.products;
if(this.state.color) {
newData = this.props.products.filter(product => {
return product.colors.some(color => {
return this.state.color.includes(color);
});
})
}
this.setState({
filtered:newData
})
}
Besides fixing the issue of comparing arrays directly, this functions returns true only when at least on of the color of a product is in the list of colors selected by the user.
Of course that you have to account the fact that newData is only defined when this.state.color is set so you should do something about it (I fixed it by assigning a default value).
Update: BTW, your code is awful, you should take some time to format your code accordingly so it is readable.
I think this can be simplified a fair bit. It's hard to tell for sure without seeing what the actually data structure of an item in the color array and what a product looks like. This new filterData method should fix it. This is what I am imagining the data looks like:
this.state.color = ["red","blue","green","purple", ...];
this.state.products = [
{name: "product1", price: 10, color: "orange"},
{name: "product2", price: 20, color: "red"},
...
]
You can also utilize some ES6 features to make your code cleaner and more readable. A big issue is you defined newData within your "if(this.state.color)" block so will always be undefined when it hits "this.setState".
First you should filter the products, and the condition in the filter being if the products color is within the color array. You can achieve this by using "indexOf" and checking if the index is greater than -1. -1 means it does not exist in the array, anything greater is the color's index position, meaning it exists.
filterData = () => {
if(this.state.color){
let { color } = this.state;
let { products } = this.props;
let newData = products.filter(product => {
if(color.indexOf(product.colors) > -1)
return product;
}
this.setState({filtered: newData})
}
}
Based on the example data I provided above, only product2 would be returned since it's color ("red") is included in the color array.
Hope this helps!

Categories

Resources