How to update object at specific index in array within React state? - reactjs

I'm building a calorie counting application using React. One of my components has in its state a list of food items:
this.state = {
items: [
{
name: 'Chicken',
selectedServing: {
label: 'breast, grilled',
quantity: 3
}
},
{
name: 'French Fries',
selectedServing: {
label: 'medium container',
quantity: 1
}
}
]
When a user changes the serving size they consumed, I have to update the properties of the item in the items[] array. For example, if a user ate another chicken breast, I'd need to change the selectedServing object in items[0].
Since this array is part of the component's state, I'm using immutability-helper. I've found that I can properly clone and mutate the state in this way:
let newState = update(this.state, {
items: {
0: {
selectedServing: {
servingSize: {$set: newServingSize}
}
}
}
});
The above code sets the servingSize for the first element in the items[] array, which is Chicken. However, I won't know the index of the object I need to update beforehand, so the 0 I hardcoded won't work. It seems that I can't store this index in a variable, because update() will think it's an object key.
How can I programmatically update an object at a specific index in a list?

An variable can be used as a key of an object.
let foo = 3
let newState = { items: { [foo]: { somthing: 'newValue' } } }
// above is equal to { items: { '3': { somthing: 'newValue' } } }
You can find the index number of 'Chicken' and save it into an variable, and use it to composit newState.

Related

How to return objects that have matching value when comparing to a separate array

In my state I have an object called foodLog which holds all entries a user enters with one of the keys being foodSelectedKey and I'm trying to return all entries that have a matching value from that key with a different array called foodFilter.
However, this doesn't work and errors out saying foodLog.filter() isn't a function - I've looked this up and it's because it's an Object (I think). Any help would be greatly appreciated!
state = {
// log food is for the logged entries
foodLog: {},
// used for when filtering food entries
foodFilter: [],
};
findMatches = () => {
let foodLog = this.state.foodLog;
let foodFilter = this.state.foodFilter;
let matched = foodLog.filter((item) => {
return foodLog.foodsSelectedKey.map((food) => {
return foodFilter.includes(food);
});
});
};
I guess the reason behind the error Is not a function is that the object can not be looped. By that it means you can not iterate an object with differend variables inside, if it has no index to be iterated like an array. The same goes for map(), find() and similar functions which MUST be run with arrays - not objects.
As far as I understand you have an object named foodLog which has an array named foodsSelectedKey. We need to find intersected elements out of foodFilter with the array. This is what I came up with:
state = {
// log food is for the logged entries
foodLog: {
foodsSelectedKey: [
{ id: 1, name: "chicken" },
{ id: 2, name: "mashroom" }
]
},
// used for when filtering food entries
foodFilter: [
{ id: 1, name: "chicken" },
{ id: 2, name: "orange" }
]
};
findMatches = () => {
let foodLog = this.state.foodLog;
let foodFilter = this.state.foodFilter;
let matched = foodLog.foodsSelectedKey.filter((key) =>
{
for (let i=0; i<foodFilter.length;i++){
if(foodFilter[i].name===key.name)
return true
}
return false;
}
);
return matched;
};
The Output is filtered array, in this case, of one element only:
[{
id: 1
name: "chicken"
}]
In order to check the output - run console.log(findMatches()). Here is the CodeSandbox of the solution. (check console at right bottom)

React Double array statement UI

I want to show the comment array in the UI using the regid and the comment map. What should I do. Help
I have an array inside an array, so I don't know how to make it visible in the UI.
In case you have array inside an array, you will need two loops or two map statements to ensure that the same is returned. Below I have tried to take a similar object as yours to show how it should look like
// assuming you have below object
const obj = {
product: {
Comments: [
{
User: { regId: 1, content: "Comment 1 Content" }
},
{
User: { regId: 2, content: "Comment 2 Content" }
}
]
}
}
const renderComments = obj.product.Comments.map(({ User: { regId, content }) => <label>{regId}: {content});

Updating state inside nested array when mapping the parent

I'm trying to update state inside a nested array with a map function and spread operator, but i don't understand how to get the key in the key/value pair to select a nested object.. There is an arrow in the code to the problematic part.
export default class ControlPanel extends Component {
state = {
words:
[
{
word: "a",
id: 1,
column: 1,
synonymns: {
all:[],
selected:[],
noneFound: false
}
}
]
}
}
updateSynonymnState = (wordId, theSynonymns) => {
const { words } = this.state
const newWords = words.map(word => {
if(word.id == wordId){
return {...word, synonymns.all: theSynonymns} //<--- synonymns.all is throwing an error, but that is the key that i need.
} else {
return word
}
})
this.setState ({words: newWords})
}
I could map the synonymns from the start but then I would loose the Id of the word which i need to select the right word..
How can I set synonymns.all = theSynonymns inside that words.map function, or is there a different way i should be able to set a nested key/value pair when mapping the parent parameter?
Immutable logic with nested values can get very tricky to get right. There are plenty of libraries that focus on that as well. For example: Immer, immutability-helper, immutable-js, and many more.
If you don't want to use another library for your state transitions, then you have to do a bit more work. You need to spread out each of the pieces of state from main object to the part you are modifying.
if (word.id == wordId) {
return { ...word, synonyms: { ...word.synonyms, all: theSynonymns } }; //<--- synonymns.all is throwing an error, but that is the key that i need.
} else {
return word;
}
You have to spread synonymns inside of word as well. Here is an example syntax of doing this:
const words = [{
word: "a",
id: 1,
column: 1,
synonymns: {
all: [],
selected: [],
noneFound: false
}
}]
const theSynonymns = ['b'];
const newWords = words.map(word => {
return {
...word,
synonymns: {
...word.synonymns,
all: theSynonymns
}
}
})
console.log(newWords)

How to use Lodash _.find() and setState() to change a value in the state?

I'm trying to use lodash's find method to determine an index based on one attribute. In my case this is pet name. After that I need to change the adopted value to true using setState. The problem is however; I do not understand how to combine setState and _.find()
As of right now I have this written. My main issue is figuring out how to finish this.
adopt(petName) {
this.setState(() => {
let pet = _.find(this.state.pets, ['name', petName]);
return {
adopted: true
};
});
}
This does nothing at the moment as it is wrong, but I don't know how to go from there!
In React you usually don't want to mutate the state. To do so, you need to recreate the pets array, and the adopted item.
You can use _.findIndex() (or vanilla JS Array.findIndex()) to find the index of the item. Then slice the array before and after it, and use spread to create a new array in the state, with the "updated" item:
adopt(petName) {
this.setState(state => {
const petIndex = _.findIndex(this.state.pets, ['name', petName]); // find the index of the pet in the state
return [
...state.slice(0, petIndex), // add the items before the pet
{ ...state[petIndex], adopted: true }, // add the "updated" pet object
...state.slice(petIndex + 1) // add the items after the pet
];
});
}
You can also use Array.map() (or lodash's _.map()):
adopt(petName) {
this.setState(state => state.map(pet => pet.name === petName ? ({ // if this is the petName, return a new object. If not return the current object
...pet,
adopted: true
}) : pet));
}
Change your adopt function to
adopt = petName => {
let pets = this.state.pets;
for (const pet of pets) {
if (!pet.adopted && pet.name === petName) {
pet.adopted = true;
}
}
this.setState({
pets
});
};
// sample pets array
let pets = [
{
name: "dog",
adopted: false
},
{
name: "cat",
adopted: false
}
]

Replace array item with another one without mutating state

This is how example of my state looks:
const INITIAL_STATE = {
contents: [ {}, {}, {}, etc.. ],
meta: {}
}
I need to be able and somehow replace an item inside contents array knowing its index, I have tried:
return {
...state,
contents: [
...state.contents[action.meta.index],
{
content_type: 7,
content_body: {
album_artwork_url: action.payload.data.album.images[1].url,
preview_url: action.payload.data.preview_url,
title: action.payload.data.name,
subtitle: action.payload.data.artists[0].name,
spotify_link: action.payload.data.external_urls.spotify
}
}
]
}
where action.meta.index is index of array item I want to replace with another contents object, but I believe this just replaces whole array to this one object I'm passing. I also thought of using .splice() but that would just mutate the array?
Note that Array.prototype.map() (docs) does not mutate the original array so it provides another option:
const INITIAL_STATE = {
contents: [ {}, {}, {}, etc.. ],
meta: {}
}
// Assuming this action object design
{
type: MY_ACTION,
data: {
// new content to replace
},
meta: {
index: /* the array index in state */,
}
}
function myReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case MY_ACTION:
return {
...state,
// optional 2nd arg in callback is the array index
contents: state.contents.map((content, index) => {
if (index === action.meta.index) {
return action.data
}
return content
})
}
}
}
Just to build on #sapy's answer which is the correct one. I wanted to show you another example of how to change a property of an object inside an array in Redux without mutating the state.
I had an array of orders in my state. Each order is an object containing many properties and values. I however, only wanted to change the note property. So some thing like this
let orders = [order1_Obj, order2_obj, order3_obj, order4_obj];
where for example order3_obj = {note: '', total: 50.50, items: 4, deliverDate: '07/26/2016'};
So in my Reducer, I had the following code:
return Object.assign({}, state,
{
orders:
state.orders.slice(0, action.index)
.concat([{
...state.orders[action.index],
notes: action.notes
}])
.concat(state.orders.slice(action.index + 1))
})
So essentially, you're doing the following:
1) Slice out the array before order3_obj so [order1_Obj, order2_obj]
2) Concat (i.e add in) the edited order3_obj by using the three dot ... spread operator and the particular property you want to change (i.e note)
3) Concat in the rest of the orders array using .concat and .slice at the end .concat(state.orders.slice(action.index + 1)) which is everything after order3_obj (in this case order4_obj is the only one left).
Splice mutate the array you need to use Slice . And you also need to concat the sliced piece .
return Object.assign({}, state, {
contents:
state.contents.slice(0,action.meta.index)
.concat([{
content_type: 7,
content_body: {
album_artwork_url: action.payload.data.album.images[1].url,
preview_url: action.payload.data.preview_url,
title: action.payload.data.name,
subtitle: action.payload.data.artists[0].name,
spotify_link: action.payload.data.external_urls.spotify
}
}])
.concat(state.contents.slice(action.meta.index + 1))
}

Resources