Update certain property of list of object in react - arrays

I want to add the addItem to the cartItems list, but if the id is the same as the item that already in the cartItems, I don't want to add another object, instead it will add the previous amountPrice and orderAmount with the new one.
Is there a way for me to achieve that?
const [cartItems, setCartItems] = useState(cartLocalStorage());
const addToCart = (id, name, image, amountPrice, orderAmount) => {
const addItem = {
id: id,
name: name,
image: image,
amountPrice: amountPrice,
orderAmount: orderAmount,
};
setCartItems([...cartItems, addItem]);
};
};

Give this a shot:
const addToCart = (id, name, image, amountPrice, orderAmount) => {
if(cartItems.find(i => i.id === id)){
setCartItems(cartItems.map(item => {
if(item.id === id){
item.amountPrice += amountPrice;
item.orderAmount += orderAmount;
}
return item;
}));
}else{
setCartItems([...cartItems, {id, name, image, amountPrice, orderAmount}]);
}
}
addToCart will check if the item is already in cartItems by id and if so, will add the values of amountPrice and orderAmount to the corresponding values of the existing element.
If, however, the id is not in cartItems, it will add a new element to the array.

I have not tested it and will probably not work but hopefully will set you in the right direction:
const [cartItems, setCartItems] = useState(cartLocalStorage());
const addToCart = (id, name, image, amountPrice, orderAmount) => {
const addItem = {
id: id,
name: name,
image: image,
amountPrice: amountPrice,
orderAmount: orderAmount,
};
const index = cartItems.findIndex(item => item.id === id);
if (index !== -1) {
cartItems[index] = {...cartItems[index], ...addItem};
setCartItems(cartItems);
} else {
setCartItems([...cartItems, addItem]);
}
};
Cheers

Related

react firestore sub collection

How can I get a list of cars for a customer
clients:
w21rffa3:
name: Johny
phone: 123123
cars:
fn1jnr12:
brand: AUDi
model: a6
number: 24f1
dsdasgf122:
brand: AUDi
model: a3
number: 62s14
My code
const ref = firestore().collection('clients');
const [clientsList, setClientsList] = useState([]);
useEffect(() => {
return ref.onSnapshot(clientsSnapshot => {
const clients = [];
const cars = [];
clientsSnapshot.forEach(client => {
const carsRef = ref.doc(client.id).collection('cars').onSnapshot(carsSnapshot => {
carsSnapshot.forEach(car => {
if (car.data().brand.length > 0) {
const {
brand,
model,
number
} = car.data();
cars.push({
id: car.id,
brand,
model,
number,
});
}
});
//Good result`
console.log('After forEach: ', cars);
});
//Bad result
console.log('After snapshot: ', cars);
const {
name,
phone
} = client.data();
clients.push({
id: client.id,
name,
phone,
cars: cars,
});
});
setClientsList(clients);
});
}, []);
cars list for customers
The error you facing is due to misuse/misunderstanding of how async/callback-based functions works. As I said in my comment - good result and bad result - bad result scripts are executed before good result due to onSnapshot is async, and you pass a callback function to it, which will be executed when data is available from firebase, so a bit "later" than the rest of the code.
Now about what can be done. The code is a bit tricky and I didnt really test it, so if anything - please, let me know.
const [clientsList, setClientsList] = useState([]);
useEffect(() => {
let carsUnsubscribeFns = [];
const clientUnsubscribeFn = ref.onSnapshot((clientsSnapshot) => {
// Reset everything and stop previously created listeners for Cars
setClientsList([]);
carsUnsubscribeFns.forEach((x) => x());
carsUnsubscribeFns = [];
clientsSnapshot.forEach((c) => {
const { name, phone } = c.data();
const client = { id: c.id, name, phone };
// In case you dont want to use optional chaining,
// remove the const client = ... line above
// and uncomment the line below
// but optional chaining is prefered anyway
// const client = { id: c.id, name, phone, cars: [] };
const carsUnsubscribeFn = ref
.doc(client.id)
.collection("cars")
.onSnapshot((carsSnapshot) => {
// Mutate the Client object directly
client.cars = carsSnapshot.docs
.map((x) => ({ id: x.id, ...x.data() }))
.filter((x) => x.brand?.length > 0);
// mark ClientsList as updated to force a rerender
// due to we mutated one of the entities inside
setClientsList((curr) => [...curr]);
});
carsUnsubscribeFns.push(carsUnsubscribeFn);
setClientsList((curr) => {
curr.push(client);
return [...curr];
});
});
// clean-up function returned from hook to stop all the listeners
return () => {
[clientUnsubscribeFn, ...carsUnsubscribeFns].forEach((x) => x());
};
});
}, []);

React - event.target.id no getting id from element

I'm trying to get the id from the element and then update it's state to the new id value.
The function where I handle the update:
updateID = (e) => {
console.log("IDFEEDING::" + this.state.id.feeding);
const { id, name } = e.target;
console.log("HereID::" + id + "," + name);
this.setState((prevState) => {
const updatedID = {
...prevState.id,
[name]: id
};
return {
id: updatedID
}
})
}
and here the element where I call the function to update the values. Update values works, but id no.. doesn't gives me erros, it's just doesn't update, I can't get the new id when I select a new radio button.
<RadioGroup id={this.state.id.feeding} aria-label="quiz" name="feeding" value={this.state.group.feeding}
onChange={(event) => {
var group = { ...this.state.group };
group.feeding = event.target.value;
this.setState({ group });
this.updateTotal(event)
var id = { ...this.state.id };
id.feeding = event.target.id;
this.setState({ id });
this.updateID(event)
}}>
Thanks in advance :)
setState is async, so it won't update the value immediately. You would need to make use of callback approach.
Also since you are executing many things within the onClick handler, I recommend you take it out into new function
updateID = ({ id, name }) => {
console.log("IDFEEDING::" + this.state.id.feeding);
console.log("HereID::" + id + "," + name);
this.setState((prevState) => {
const updatedID = {
...prevState.id,
[name]: id
};
return {
id: updatedID
}
})
}
onChange= (event) => {
var group = { ...this.state.group };
const {id, value, name } = event.target;
group.feeding = value;
var id = { ...this.state.id };
id.feeding = id;
// Use one setState instance to update both
this.setState({ id, group }, () => {
// using on callback
this.updateTotal(group);
this.updateID({id, name})
});
}
}
...
<RadioGroup id={this.state.id.feeding} aria-label="quiz" name="feeding" value={this.state.group.feeding}
onChange={this.onChange}>

Problem with Hooks: useEffect, useMutation and useState working together

I have a function that is using react-table as a datagrid. It is being initially populated from Apollo in a parent component via local state with each line in the grid an object in an array.
When changes occur in a cell in the grid the whole line object is written to state.
I am trying to use useEffect to trigger a mutation that writes these changes in state back to the database, but I am struggling with two main things:
the mutation is not writing back to the database (the mutation does work in the graphql playground though)
understanding how to send only the changed row back to the mutation.
The Main Function (part of)
function Table2({ columns, data }) {
const [lines, setLines] = useState(data);
const [updateLine, {loading, error }] = useMutation(UPDATE_ITEM_MUTATION, {
variables:{ ...lines}
});
useEffect(() => {
updateLine
},[lines]);
const updateMyData = (rowIndex, columnID, value) => {
setLines(getLines =>
getLines.map((row, index) => {
if (index === rowIndex) {
console.log(row)
return {
...lines[rowIndex],
[columnID]: value
};
}
return row;
})
);
};
and the mutation...
const UPDATE_ITEM_MUTATION = gql`
mutation UPDATE_LINE_MUTATION(
$id: ID!,
$description: String,
$project:Int
$category:Int
$account:Int
$amt:Int
$units:String
$multiple:Int
$rate:Int
){
updateLine(
where:{id: $id},
data: {
description: $description
project: $project
category: $category
account: $account
amt: $amt
units: $units
multiple: $multiple
rate: $rate
}) {
id
description
amt
}
}
`
I'd be really grateful for some advice.
Thanks
I don't think you need to use useEffect, you can trigger the mutation in your update:
function Table2 ({ columns, data }) {
const [lines, setLines] = useState(data)
const [updateLine, { loading, error }] = useMutation(UPDATE_ITEM_MUTATION)
const updateMyData = (rowIndex, columnID, value) => {
const updatedLine = { ...lines[rowIndex], [columnID]: value }
updateLine({ variables: { ...updatedLine } })
setLines(getLines => getLines.map((row, index) => (index === rowIndex ? updatedLine : row)))
}
}
If you did want to use useEffect, you could e.g. keep the last changed line in a state variable and then use that to trigger the update:
function Table2 ({ columns, data }) {
const [lines, setLines] = useState(data)
const [updateLine, { loading, error }] = useMutation(UPDATE_ITEM_MUTATION)
const [updatedLine, setUpdatedLine] = useEffect(null);
useEffect(()=>{
// call your mutation
}, [updatedLine]);
const updateMyData = (rowIndex, columnID, value) => {
const updatedLine = { ...lines[rowIndex], [columnID]: value }
setUpdatedLine(updatedLine);
updateLine({ variables: { ...updatedLine } })
setLines(getLines => getLines.map((row, index) => (index === rowIndex ? updatedLine : row)))
}
}

How do I get the TOTAL price from the updated state? (reactjs)

I need to dynamically update the total when an item is added
selectedProducts is the array being updated onClick with the selected item, the item includes the price (in the object: 'price'), i need to get the price of the total items, and update totalPrice accordingly.
* I am trying to keep everything in one function if possible *
class App extends Component {
state = {
products: products,
selectedProducts: [],
totalPrice: 0,
};
handleQuantityChange = id => {
const carsSelected = this.state.selectedProducts;
const price = products.map(id => id.price).find((id)=> {
return id
} )
const priceAddition = price
const oldPrice = this.state.totalPrice;
const newPrice = oldPrice + priceAddition;
this.setState({
selectedProducts: [...carsSelected, id],
totalPrice:newPrice,
});
};
You can simplify your method with a functional form of setState:
handleQuantityChange = id => {
const product = products.find(product => product.id === id)
this.setState(state => ({
selectedProducts: [...state.selectedProducts, id],
totalPrice: state.totalPrice + product.price,
}));
};
Because find method does return matched record as array object.
so you have replace above your find method line with these lines
const product = products.find(product => product.id === id)
const {price=0}=product[0] && product[0] || {};
ive solved the issue, all i had to do was:
handleQuantityChange = id => {
const carsSelected = this.state.selectedProducts
const oldTotal = this.state.totalPrice
const newPrice = id.price + oldTotal
this.setState({
selectedProducts: [...carsSelected, id],
totalPrice: newPrice
});
};
thanks for everyone help

I need to do a lodash filter based on the key of a child element

I am trying to use lodash to filter objects in a react app.
const filteredContacts = _(contacts)
.map((contact, id) => ({_id: id, ...contact}))
.filter(contact => !(this.state.filter && contact.lastName.toLowerCase().indexOf(this.state.filter) == -1 && contact.firstName.toLowerCase().indexOf(this.state.filter) == -1))
.filter(c => {
_(c.courses)
.map((course, id) => ({_id: id, ...course}))
.each(course => {
console.log(course._id)
return (course._id == this.state.courseId)
})
})
This is pulling from a firebase database, so there are nodes which are just a key, no value, that then have children. In this case I have capitalized where those are below.
contacts > USERID > courses > COURSEID > coursedata
or
contacts: {
-uf39uhef2: {
name: blah,
courses: {
-dfh92ehfdsfhw: {
coursename: name
}
}
}
}
I know that my second filter is all kinds of wrong - I just pasted the last iteration of a million tries. this.state.courseId is the course ID I want to filter by - how can I accomplish this?
I want to return all users that have a course ID matching the one set in state.
In the filter check if the contact has courses, and if coureses[this.state.courseId] is truthy:
const filteredContacts = _(contacts)
.map((contact, id) => ({_id: id, ...contact}))
.filter(({ courses }) => _.has(courses, this.state.courseId));
const contacts = {
'-uf39uhef2': {
name: 'blah',
courses: {
'-dfh92ehfdsfhw': {
coursename: 'name'
}
}
},
'-willBeFiltered': {
name: 'willBeFiltered',
courses: {
'-willBeFiltered': {
coursename: 'name'
}
}
}
};
const courseId = '-dfh92ehfdsfhw'; // courseId = this.state.courseId
const filteredContacts = _(contacts)
.map((contact, id) => ({_id: id, ...contact}))
.filter(({ courses }) => _.has(courses, courseId));
console.log(filteredContacts);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.2/lodash.min.js"></script>
You can also do this without lodash:
const filteredContacts = Object.keys(contacts)
.map((id) => ({_id: id, ...contacts[id]}))
.filter(({ courses }) => courses && courses[this.state.courseId]);
just use _.pickBy to to get needed users
_.pickBy(contacts, (user) => {
return _.chain(user)
.get('courses')
.keys()
.includes(courseId)
.value();
});
another way
_.pickBy(contacts, (user) => {
return _.has(user, ['courses', courseId]);
})

Resources