My main state:
this.state = {
list: []
}
I have a form w/c takes 2 separate arguments: listItem and quantity
render () {
return (
<div>
{this.state.error && <p>{this.state.error}</p>}
<form onSubmit={this.handleAddOption}>
<input type="text" placeholder="description" name="description" /> <br/>
<input type="number" placeholder="quantity" name="quantity" /> <br/><br/>
<button>Add Grocery</button>
</form>
</div>
);
}
}
So basically the two forms are being submitted to the handleAddOption function:
handleAddOption(description, quantity){
if (!description && !quantity) {
return 'Enter valid description and quantity to add item';
} else if (this.state.list.indexOf(description) > -1) {
// must update the quantity
}
this.setState((prevState) => ({
list: prevState.list.concat(description, quantity)
}));
}
So basically the list is an array. There are two things I want to do here:
1. I need to add this two to my main array list state and render them side by side on the screen like:
item1 1
item2 2
As of the moment, I am only concatenating both as text w/c is not a good idea since I need to update the quantity later.
I want to find a way to update the quantity if the same description was placed to the to the form for instance:
1st Input:
item1 1
2nd Input:
item1 5
So it must update the item1 quantity to 5
Any idea how I can handle this?
In order to save the information to an array, we need to figure out the location of the description inside of the array.
You're using the concat function in your example. This function add the two items to the array, but as separate entries. I would recommend you to save the information to a dictionary, this combines the values into a single entry. In JavaScript, we can do this using objects:
prevState.list.push({
description: description,
quantity: quantity
});
In order to get the description from the object, we use the dot notation.
We would save an object to a variable, let's name it entry, and access the description like:
entry.description
In order to make this work, we are supposed to change the function a little bit. Since we can't just check for the presence of a value, we need to loop over the entries of the list. When we do, we have to check whether the descriptions match, if none does, we add the new entry
handleAddOption(description, quantity){
var hasBeenFound = false;
prevState.list.forEach(function (entry) {
if (entry.description === description) {
hasBeenFound = true;
entry.quantity = quantity;
}
});
if (!hasBeenFound) {
elements.push({description: description, quantity: quantity})
}
}
I hope my description made a little sense and that you are able to continue.
Your handleAddOption function edit like this.
handleAddOption = (e) => {
e.preventDefault();
let description = e.target[0].value;
let quantity = e.target[1].value;
if (!description && !quantity) {
return 'Enter valid description and quantity to add item';
} else {
const list = this.state.list.map((el, i) => {
if (el.indexOf(description) > -1) {
// must update the quantity
}
return (el)
});
}
this.setState((prevState, props) => ({
list: prevState.list.concat(description + ' ' + quantity)
}));
}
Related
I'm trying to make an add to cart function that first checks if the item being added is already in the cart. If it's in the cart, update its quantity property. If not in the cart, add the entire object to the cart. I think my problem is I'm getting the logic wrong inside my "ADD_ITEM_TO_CART" mutator function.
This is my store with some console.logs() from when I click "addToCart()"
state: {
checkoutCart: [],
},
actions: {
cartAdd({ commit }, payload) {
commit("ADD_ITEM_TO_CART", payload);
},
},
mutations: {
ADD_ITEM_TO_CART(state, payload) {
//CONSOLE.LOG()'s
console.log("state.checkoutCart[0]", state.checkoutCart[0]);
// eslint-disable-next-line
console.log("state.checkoutCart[0].item", state.checkoutCart.item);
console.log("state.checkoutCart", state.checkoutCart);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
else {
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
}
https://i.imgur.com/rjOOljN.png
I thought this code would work, but it ALWAYS executes the ELSE condition and adds to cart like the
if (state.checkoutCart.includes(payload.item))
isn't being recognized or working at all.
https://i.imgur.com/LLB790Z.png
VueX devtools shows the same thing. An "item" object inside an object inside an array.
I also tried:
ADD_ITEM_TO_CART(state, payload) {
console.log("add_item_to_cart"); <---ONLY PART THAT SHOWS UP IN CONSOLE.LOG() WHEN EXECUTED
//LOOP THROUGH ALL ARRAY ENTRIES TO GAIN ACCESS TO state.checkoutCart.item
for (let i = 0; i < state.checkoutCart.length; i++) {
console.log("i=", i);
console.log("state.checkoutCart.item", state.checkoutCart.item);
//IF ITEM ALREADY IN checkoutCart, UPDATE IT'S QUANTITY
if (state.checkoutCart[i].item.includes(payload.item)) {
state.checkoutCart.quantity += payload.quantity;
console.log("Item already in cart");
return;
};
}
//IF ITEM NOT IN checkoutCart, UPDATE THE QUANTITY PROPERTY AND ADD ITEM TO CART
payload.item.quantity = payload.quantity;
state.checkoutCart.push(payload);
},
because I figured I needed to loop through all the array entries. BUT the for loop doesn't even run, and with this code nothing gets added to the cart at all.
I can't figure out what I'm doing wrong here. Can somebody help? Is my syntax wrong? Or is my logic? Am I accessing the arrays/objects incorrectly? How do I write the "ADD_ITEM_TO_CART" mutator function correctly? I've literally spent all day on this and my brain is shutting down.
EDIT:
https://i.imgur.com/bkU8YSo.png
PAYLOAD
<div v-for="item in items"> <--ACTUALLY PROP FROM PARENT COMPONENT BUT SAME IDEA
<p>
Qty
<select v-model="quantity">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
</p>
<p>
<button type="button" #click="addToCart()">
Add to Cart
</button>
</p>
</div>
let quantity = ref("1");
const addToCart = () => {
console.log("addToCart Running");
store.dispatch("cartAdd", { item: item.value, quantity: quantity.value });
};
That is because your if condition is not checking for what you think.
Array.prototype.includes checks if a value is in the array but there are two cases:
the value is a primary type (string, number, boolean, ...). It compares by value.
the value is an object. Then it compares by reference.
So here, you are checking if the reference of your item object is already included in the array. But it's not, since it's a new object.
Solution: check if there is an object with the same values, not reference.
You can use the some method, and you have to write a condition that checks if two items are equals.
Here is an example if your items have an id:
if (state.checkoutCart.some(item => item.id === payload.item.id))
The problem is indeed inside ADD_ITEM_TO_CART mutation.
As Kapcash has pointed out, two objects having the same properties and the same values are not the same.
In other words, .includes() checks for identity, not equality. To better understand this, consider this example:
const a = { foo: 'bar' }
const b = [{ foo: 'bar' }]
const c = [a]
const d = [{ ...a }]
console.log(b.includes(a)) // false
console.log(c.includes(a)) // true
console.log(d.includes(a)) // false
To get past this, use Kapcash's answer.
I'll just mention the standard way of dealing with this problem is using unique identifiers on objects (e.g: uuids).
Once you fix the above, it's still not going to work, because you'll run into the following problem: inside the if you're attempting to alter state.checkoutCart's quantity. And an array does not have a quantity property.
The proper logic to achieve the desired functionality is (assuming the unique identifier on checkoutCart members is item._id, from the pictures you posted):
ADD_ITEM_TO_CART(state, payload) {
// find the index of a cart element having same `item._id` value as the payload
const index = state.checkoutCart.findIndex(
(el) => el.item._id === payload.item._id
)
if (index > -1) {
// if the index is higher than `-1`, an element was found
// create a copy, update its quantity and then
// replace the original element with the copy
const copy = { ...state.checkoutChart[index] }
copy.quantity += payload.quantity
state.checkoutCart.splice(index, 1, copy)
} else {
// no element found, add payload to `checkoutCart`
state.checkoutCart.push(payload)
}
}
Side note: None of your items should contain a quantity property. That's the cart's responsibility, not the item's. By adding it to the item you end up with two sources of truth which you'd need to keep in sync. You don't want this type of problem.
The question may seem a little confusing so let me explain the situation, i have a category field, that when it's chosen, i get data from api based on that category, for example when you choose category A, you may get the id's 1 to 3 or when you chose category B, you get the id's of 8 to 11, so what i'm trying to do is create a Filter array that pushes these values with id's so when user types something like sth in input with the id of 1, this objects gets pushed to the array, something like below:
Filters = [
{
id:1,
value: sth
}
]
so what i have basically done is something like this:
this.props.Filters.map(item => (
<div className="from" key={item.id} id={item.id} >
<span className="title">{item.feature}</span>
<input type="text" placeholder="..." id={item.id} onChange={FilterPush}></input>
</div>
))
and the function is:
const Filters = []
const FilterPush = (e) => {
const filter = {
id: e.currentTarget.getAttribute("id"),
value: e.currentTarget.value
}
Filters.push(filter)
}
the problem i'm having here is every time a user is typing something an object is pushed to Filters,
so when user tries to type sth the Filter gets equal to:
Filters={
{id:1,value:s},
{id:1,value:st},
{id:1,value:sth}
}
but i want it to be equal to this
Filters={
{id:1, value:sth}
}
bear in mind that the id's are dynamic and i don't know them, and there's not just one input, i mean we can have 3 inputs with diffrent id's that should get send to the server.
Just filter out existing id:
const FilterPush = (e) => {
let id = e.currentTarget.getAttribute("id")
const filter = {
id,
value: e.currentTarget.value
}
Filters = Filters.filter(item => item.id !== id) // filter out existing id
Filters.push(filter)
}
It's very simple all you have to do is just check the pushing element's id is already in the Filters array before pushing the element.
const FilterPush = (e) => {
let id = e.currentTarget.getAttribute("id")
const filter = {
id: e.currentTarget.getAttribute("id"),
value: e.currentTarget.value
}
Filters.map(item=>{
if(id == item.id){
available = true;
}
})
if(!available){
Filters.push(filter)
}
}
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
This task is tough one for me. Please don't get mad. I'm making dynamic inputs to able for user add more informartion.Expected output if both field was filled, push it to the array. I have one datalist and one input. For both of them I want to use one onChange function to make validation and push to the array. Right now, in onChange function I'm checking datalist, does value was typed or selected from options. The problem is that when I'm storing in state, it only saves one sigle value or doesn't push to array. I commented where is the problem in the code. I have tried to reproduced my code : https://jsfiddle.net/armakarma/qwg3j2fa/9/
{this.state.arrayOfInput.map((item, idx) => {
return (
<>
<input
type="text"
name="address"
list="data-list"
onChange={e => this.onChooseAddress(e)}
/>
<datalist id="data-list">
{this.state.adresses.map((item, idx) => {
const { id, address } = item
return <option key={idx} data-value={id} value={address} />
})}
</datalist>
<input
name="contact"
className="event_time-inputs event_table_textarea"
placeholder="Contact"
onChange={e => this.onChooseAddress(e)}
/>
</>
)
})}
onChange function:
onChooseAddress(e) {
const { adresses } = this.state
let address_id = ""
let new_address = ""
let whoIsMetting = ""
// checking if value from "contact" input
if (e.target.name === "contact") {
whoIsMetting = e.target.value
}
// checking if value is selected from options in datalist
for (let i = 0; i < adresses.length; i++) {
if (
e.target.value === adresses[i].address &&
e.target.name === "address"
) {
address_id = adresses[i].id
}
}
//if user typed value
if (!address_id && e.target.name === "address") {
new_address = e.target.value
}
// PROBLEM IS HERE!!!! if both fields was filled pushing to array
if ((address_id || new_address) && whoIsMetting) {
let interviewDetails = {
address_id: address_id,
new_address: new_address,
whoIsMetting: whoIsMetting,
}
this.setState(prevState => ({
arrayOfAddresses: [...prevState.arrayOfAddresses, interviewDetails],
}))
}
}
Your problem is, that only one of the three variables address_id, new_address and whoIsMetting can have a value at a call of onChooseAddress.
You can never have a value in whoisMetting and one of the others together, because whoIsMetting only gets a value if onChooseAddress is called from the contact input and the others if it is called from the address input.
I suggest you either stash partly correct values in a different variable (outside of the method ofc) and check for completion before pushing them to the arrayOfAddresses OR simply push partly correct entries directly to arrayOfAddresses and check for completion before using an entry of this array.
I am trying to implement a filter function that is able to search in two separate JSON fields when a user types in a search bar. Searching the whole JSON returns errors and if I repeat this function, the two similar functions cancel each other out.
My current filter function:
let filteredOArt = origArt.filter((origAItem) => {
return origAItem.authors.toLowerCase().includes(this.state.search.toLowerCase())
});
I want to be able to have the search look within the "authors" field as well as a "description" field.
Before the React render, I have this function listening to the state:
updateSearch(event) {
this.setState({ search: event.target.value })
}
Then my search function is in an input field in the React return:
<h6>Search by author name: <input type="text" value={this.state.search} onChange={this.updateSearch.bind(this)} /></h6>
You can tweak the function a bit like this
let filteredOArt = origArt.filter((origAItem) => {
return (
(origAItem.authors.toLowerCase().includes(this.state.search.toLowerCase())||
(origAItem.description.toLowerCase().includes(this.state.search.toLowerCase())
)
)
});
You actually can do a filter for both fields.
Given you have your searchValue and your array with the objects you could filter this way:
const filterByAuthorOrDescription = (searchValue, array) =>
array.filter(
item =>
item.authors.toLowerCase().includes(searchValue.toLowerCase()) ||
item.description.toLowerCase().includes(searchValue.toLowerCase())
);
const filtered = filterByAuthorOrDescription(this.state.search, articles);
filtered will now contain an array of objects that contain your searchValue in either description or authors that you can map through.
You could use some to check if the filter is positive for at least one field :
let filteredOArt = origArt.filter(origAItem => ['authors', 'description'].some(field => origAItem.[field].toLowerCase().includes(this.state.search.toLowerCase())))
Just iterate over the different field names you want to use.
Some will return true if any of the fields mentioned contains your string and avoid repetitions in your code.
Long syntax :
origArt.filter(origAItem => {
return ['authors', 'description'].some(field => origAItem.[field].toLowerCase().includes(this.state.search.toLowerCase()))
})