React manage array multidimensional by its index - javascript

I have a problem how to update(in this case Add or Remove Cargo) my multidimensional array based on the selected index. The parent array has been successful, but I am confused for the child how to process the data when clicking the Add Cargo button to add cargo and the Remove Cargo button to delete all cargo based on the selected index.
Please help. This is my codesandbox code
Sorry for the view, maybe is not good enough

You will need to provide index to the functions to add and/or delete cargos. In the function update the nested fields by mapping over the inputFields. While calling the handleAddCargo, pass the index and while calling handleRemoveCargo, pass the index as well as finalIndex which is the cargo array index.
const handleAddCargo = (parentFiledIndex) => {
const updatedInputFields = inputFields.map((item, i) => {
if(parentFiledIndex === i){
return {...item, cargo: item.cargo.concat({
cargoId: '',
cargoDescription: "",
cargoHsCode: ""
})}
}else{
return item
}
});
setInputFields(updatedInputFields);
console.log("add by its index cargo here");
};
const handleRemoveCargo = (parentFiledIndex, cargoIndex) => {
const updatedInputFields = inputFields.map((item, i) => {
if(parentFiledIndex === i){
return {...item, cargo: item.cargo.filter((cargo, c) => c !== cargoIndex)}
}else{
return item
}
});
setInputFields(updatedInputFields);
console.log("remove by its index cargo here");
};
updated working solution is here
https://codesandbox.io/s/reverent-bose-c2nkk
quick note -
try not to use array indexes while rendering lists. For eg, use some library to generate unique id

Related

How do i update value of a key in an object inside an Array in React?

I have a dynamic list of items a user adds. I want to avoid duplicating when a user adds an item already present in the list. My list looks like
itemList = [ {itemId:1, name:"x", quantity:5}, {itemId:4, name:"y", quantity:2}]
so now if a user adds item x with quantity 2 i want the object with item x to update the quantity to 7 rather than adding a whole new object.
I am using find() method to get the item already present and storing it to a variable, itemObj is the item the user recently added.
let alrItem = state.itemList.find(
(e) => e.itemId === itemObj.itemId
);
let newItem = alrItem;
newItem.quantity += itemObj.quantity;
How do I merge this newItem to the itemList so that it just updates the quantity of that specific item?
What you are doing is finding the object in the itemList array and then mutating the state directly. State should not be mutated directly.
Instead of using .find() method, use the .map() method to iterate over the array and update the quantity of the item that matches with the id of the new item.
let updatedItemList = state.itemList.map((item) => {
if (item.itemId === itemObj.itemId) {
return { ...item, quantity: item.quantity + itemObj.quantity };
}
return item;
});
// update the state
setItemList(updatedItemList);
Note that above code will not do anything if the item isn't already present in the itemList. Ideally, you should also handle the case where the new item isn't already present in the itemList. In this case, you should just add the new item in the itemList.
To handle this case, all you need is a extra variable that can be used to know whether the if condition inside the .map() method evaluated to true or not.
let exists = false;
let updatedItemList = state.itemList.map((item) => {
if (item.itemId === itemObj.itemId) {
exists = true;
return { ...item, quantity: item.quantity + itemObj.quantity };
}
return item;
});
// if the item isn't present in the list, add it in the "updatedItemList"
if (!exists) {
updatedItemList.push(itemObj);
}
// update the state
setItemList(updatedItemList);
let itemList = [ {itemId:1, name:"x", quantity:5}, {itemId:4, name:"y", quantity:2}]
let itemObj = {itemId:1, name:"x", quantity:2}
const target = itemList.find(element =>
element.itemId === itemObj.itemId
);
if (target) {
target.quantity = target.quantity + itemObj.quantity;
} else {
itemList.push(itemObj);
}
console.log('itemList: ' + JSON.stringify(itemList));
OUTPUT:
itemList: [{"itemId":1,"name":"x","quantity":7},{"itemId":4,"name":"y","quantity":2}]

How would I go about deleting a single item in Firebase?

How would I go about deleting just a single item?
Provided I know the 'name' value ("To add") but I don't know the item ID value (-M0qUq...).
The following code works to delete the item, but I want to make it dynamic and not have to hardcode in the ID value.
handleRemove = (item) => {
db.ref('/items/-M0qUPNnHRbZAb1R3690/').remove();
}
Try the following:
let query = db.ref('items').orderByChild("name").equalTo("To add");
db.once('value').then((snapshot) => {
snapshot.forEach((subSnapshot) => {
let key = subSnapshot.key;
db.ref('items').child(key).remove();
});
});
First add a query equalTo, then iterate inside the returned result to be able to retrieve the key and then use remove() to delete.

How to search a value in an array inside another array

I have a problem of find a value in an array inside another array, and use the result to setState()
This is the initialState:
this.state =
{
initialStudents:[
{name:"str1",tags;["str","str",...],...},
{name:"str2",tags;["str","str",...],...},
...
],
students: [
{name:"str1",tags;["str","str",...],...},
{name:"str2",tags;["str","str",...],...},
...
]
}
The code i use to find the tags:
findTag = (tags, target) => {
tags.filter(tag => {
return tag.toLowerCase().search(target.toLowerCase()) !== >-1;
});
};
filterTag = e => {
let updatedList = this.state.initialStudents;
updatedList = updatedList.filter(student => {
return this.findTag(student.tags, e.target.value);
});
this.setState({ students: updatedList });
};
The filterTag does not update the students state
To solve your problem, I made a few edits and put them all in this working codesandbox example.
First, I changed your findTag function to something like this:
// pass in the tags from the student, and the target tag you're searching for.
// -> return true if 1 or more matching tag, false otherwise
findTag = (tags, targetTag) => {
// make sure you return something!
return tags.filter(tag => {
// check if current tag in arr matches target tag (case insensitive)
return tag.toLowerCase() === targetTag.toLowerCase();
}).length > 0; // check if there's 1 or more matching tag
};
Next, I updated the filterTag function in a few ways:
Immutably copy this.state.initialStudents into the local updatedList array. This is necessary so you don't mess up the current state before running this.setState!
Pass the value of the input via this.state.filterTag instead of e.target.value. This way, you'd update the filter when you click the button instead of on every time you press a key.
Here's how these changes look:
filterTag = e => {
// immutably copy initial student data
let updatedList = this.state.initialStudents
.map(student => ({
name: student.name,
tags: [...student.tags]
}))
// remove students w/out filter tag
.filter(student => {
return this.findTag(student.tags, this.state.filterTag);
});
// update state with new student list
this.setState({ students: updatedList });
};
A few other improvements I made:
Instead of manually setting data in initialStudents and students, I made them immutably copy the same data set from the const initialStudents data set. This could be done in the componentDidMount lifecycle method if you're fetching students from a database.
I fixed your student object declarations - you put tags;["str"...] which is invalid - the semicolon ; should be a normal colon :
I changed some "str" values to "str2" to make them unique between students
Let me know if you have questions about the codesandbox or anything else :D Hope it helps!

Delete Session Storage - Shopping Cart Project

I hope this is a good question. I am working on a shopping cart project. I have been scouring the internet through different tutorials on shopping carts. I am attempting to write mine in Vanilla Javascript. I am having a problem with removing shopping cart items from session storage.
Below is what is currently in my session storage. As you can see it is an Array of objects.
[{"id":"8","name":"Candy
Skull","price":"20000","image":"../images/candyskull-
min.JPG","qty":"1"},{"id":"5","name":"Upsidedown
House","price":"20000","image":"../images/upsidedownhouse-
min.JPG","qty":"1"},{"id":"6","name":"Brooklyn
Window","price":"30000","image":"../images/brooklynwindow-
min.JPG","qty":"1"},{"id":"4","name":"Hand That
Feeds","price":"40000","image":"../images/handthatfeeds-
min.JPG","qty":"1"}]
I want to loop through the array and remove the matching object from the cart storage.
Below is the JS Code used to generate the .remove-from-cart buttons. As you can see it includes all the dataset information.
<td>
<span class="remove-from-cart">
<b data-id="${value.id}" data-name="${value.name}" data-
price="${value.price}" data-image="${value.image}" data-
qty="${value.qty}">X</b>
</span>
</td>
To test the functionality of what I have done so far you can visit www.dancruzstudio.com/shop
The function that I can't get to work properly is the removeFromStorage() function. For some reason when comparing an object to the objects in the array I'm never getting back a true boolean value, even when there are items in the cart that should match. Where am I going wrong? I hope someone can help. Below is a copy of my JS code.
The method I am using is having an identical dataset value in the remove item button generated by JS and then parsing that dataset into an object and comparing it to the objects in the session storage array which is called shopItems inside the removeFromStorage() function. I hope this information is enough for someone to see my problem. Thank you in advance.
// Remove item from DOM
document.querySelector('#cart-list').addEventListener('click',
removeFromCart)
function removeFromCart(e) {
if(e.target.parentElement.classList.contains('remove-from-cart')) {
//Remove from DOM
e.target.parentElement.parentElement.parentElement.remove();
//Remove from Session Storage
removeFromStorage(e.target.dataset);
}
}
// remove from Session storage
function removeFromStorage(removedItem){
let shopItems;
if(sessionStorage['sc'] == null){
shopItems = [];
} else {
shopItems = JSON.parse(sessionStorage['sc'].toString());
}
var compare = JSON.parse(JSON.stringify(removedItem))
shopItems.forEach(function(item, index){
if(compare === item){
console.log(compare);
console.log(item);
// shopItems.splice(index, 1);
}
});
sessionStorage['sc'] = JSON.stringify(shopItems);
}
You can not compare objects like this.
let a = {p:1};
let b = {p:1};
console.log(`a ===b ? ${a===b}`);
If your objects are fairly simple you can try comparing their stringify representation:
let a = {p:1};
let b = {p:1};
const compare = (x,y) => {
return JSON.stringify(x) === JSON.stringify(y);
}
console.log(`a === b ? ${compare(a,b)}`);
or write your custom compare function (that may be challenging):
Compare JavaScript objects
Since your objects are decorated with an id, the easisest way would be to idntify them by that:
let storage = [{"id":"8","name":"Candy Skull", "price":"20000","image":"../images/candyskull- min.JPG","qty":"1"},{"id":"5","name":"Upsidedown House","price":"20000","image":"../images/upsidedownhouse- min.JPG","qty":"1"},{"id":"6","name":"Brooklyn Window","price":"30000","image":"../images/brooklynwindow- min.JPG","qty":"1"},{"id":"4","name":"Hand That Feeds","price":"40000","image":"../images/handthatfeeds- min.JPG","qty":"1"}];
let items = [{"id":"6","name":"Brooklyn Window","price":"30000","image":"../images/brooklynwindow- min.JPG","qty":"1"}, {"id":"5","name":"Upsidedown House","price":"20000","image":"../images/upsidedownhouse- min.JPG","qty":"1"}];
const pluck = (acc, crt) => {
acc.push(crt.id);
return acc;
};
let storageIndexes = storage.reduce(pluck, []);
let itemsIndexes = items.reduce(pluck, []);
let removeIndexes = [];
itemsIndexes.forEach(id => removeIndexes.push(storageIndexes.indexOf(id)));
console.log('storage', storage);
console.log('removed items', items);
removeIndexes.sort().reverse().forEach(index => storage.splice(index,1));
console.log('remaining storage', storage);

React setState not updating reduced array

I have a pretty simple custom component: two select lists with buttons to move the options from the available (left) list to the selected (right) list. Naturally, the moved element should no longer show up on the list it was moved from. Though both buttons successfully add the element to the target, it doesn't remove from the source, because when I pass the reduced array of items to setState, the render still returns with the original list.
EDIT posting most of the component code for clarification. The problem methods are the addItems and removeItems, where setState is called. In both cases, whichever array property is being reduced/filtered is the one not updating; the one being added to always updates properly.
... imports
interface JoinedListState {
availableItems: ListItem[]
selectedItems: ListItem[]
}
export class JoinedList extends React.Component<JoinedListState, any>{
// Create new arrays of the proper available and selected then set the new
// state
private addItems(newItems: ListItem[]) {
let oldSelected = this.props.selectedItems;
oldSelected.push.apply(oldSelected, newItems);
let newSelected = oldSelected.sort((a, b) => {
let nameA = a.value.toUpperCase();
let nameB = b.value.toUpperCase();
if (nameA < nameB) {
return -1
}
return 1
});
let newAvailable = this.props.availableItems
.slice(0) // updated on recommendation of Sasha Kos
.filter((item) => {
return newItems.findIndex(i => i.id == item.id) == -1
});
this.setState({
availableItems: newAvailable,
selectedItems: newSelected
});
}
// Create new arrays of the proper available and selected then set the
//new state
private removeItems(removedItems: ListItem[]) {
.. same approach as addItems
let newSelected = this.props.selectedItems.filter((item) => {
// return only the items whose id does not exist on the newly
//removed items list
return removedItems.findIndex(i => i.id == item.id) == -1
})
this.setState({
availableItems: newAvailable,
selectedItems: newSelected
})
}
// Get the selected items by querying the DOM and send them to function
// to update state
addSelected(event: React.FormEvent<HTMLButtonElement>) {
// Code removed for brevity: uses the event object to find the
//selected objects and builds a ListItem array called 'selected'
//to pass to addItems
this.addItems(selected)
}
removeSelected(event: React.FormEvent<HTMLButtonElement>) {
// Code removed for brevity: uses the event object to find the
//selected objects and builds a ListItem array called 'selected'
//to pass to addItems
this.removeItems(selected)
}
render() {
let aItems = this.renderOptionList(this.props.availableItems),
sItems = this.renderOptionList(this.props.selectedItems);
return (
<div className='joined-list-container'>
<select key='available_list' className='available-list form-
control' multiple>
{aItems}
</select>
<span className='button-container'>
<button key='button1' className='btn btn-success'
onClick={this.addSelected.bind(this)}>
<span className='glyphicon glyphicon-chevron-right'>
</span>
</button>
<button key='button2' className='btn btn-danger'
onClick={this.removeSelected.bind(this)}>
<span className='glyphicon glyphicon-chevron-left'>
</span>
</button>
</span>
<select key='selected_list' className='selected-list form-
control' multiple>
{sItems}
</select>
</div>
)
}
renderOptionList(items: ListItem[]) {
return items.map((item, idx) => {
let key = `${item.value}_${idx}`
return (
<option value={item.id} key={key}>{item.value}</option>
)
})
}
}
(Sorry for any flawed formatting, posting was tricky)
When this kicks off the new render, the selectedItems list is properly updated with the new item(s), but the availableItems is always the original array (yes I've ensured that the newAvailable array is properly filtered down), and even when I try
this.setState({
availableItems: [],
selectedItems: newSelected
})
I get the original availableItems array on the next render.
Is there some nuance to returning similar-but-shorter arrays to state via setState? I can't find anything referencing this behavior, and not sure what I'm missing.
Thanks
This is the issue:
let oldSelected = this.props.selectedItems;
oldSelected.push.apply(oldSelected, newItems);
You are updating this.props.selectedItems here, but for availableItems:
let newAvailable = this.props.availableItems
.slice(0) // updated on recommendation of Sasha Kos
.filter((item) => {
return newItems.findIndex(i => i.id == item.id) == -1
});
Here, you do not directly update this.props.availableItems. The reason this matters is that when you call setState and render is triggered these methods:
let aItems = this.renderOptionList(this.props.availableItems),
sItems = this.renderOptionList(this.props.selectedItems);
are using this.props to return arrays, NOT this.state. this.props.selectedItems has changed, and thus returns a different array, while this.props.availableItems has not changed.
tl;dr - use this.state instead of this.props when passing the arrays to your renderOptionList method.
According to mozilla docs Array.prototype.filter should create new array, but described symptoms says that you just get 2 references to one array so there is no rerender. So please try this
let newAvailable = this.props.availableItems
.slice(0) /* clones your array */
.filter((item) => {
return newItems.findIndex(i => i.id == item.id) == -1
});
this.setState({
availableItems: newAvailable,
selectedItems: newSelected
});

Categories

Resources