I think it might be silly question to ask but trust I am new to React . I am trying to push new key and value to array of object but I am not able to do it . Could someone please help me how to achieve my goal. Thanks
Code
this.state= {
cartItems=[
{name:'item1',price:'$23'},
{name:'item2',price:'$26'},
{name:'item3',price:'$24'},
]
I want to add new value like quantity:0 in the end of array of object. Please help me
If you want to update state adding a property to each object, you should just use setState in combination with map. Array.prototype.map allows you to transform each object, like this:
this.setState(state => {
cartItems: state.cartItems.map(cartItem => ({
...cartItem, // Keep all old properties
quantity: 0 // Add quantity
})
})
You should do this only if you're calling setState after the initializer. If you need to modifiy the data right when assigning to this.state, just map the array directly.
Maybe try this
const newState = this.state.cartItems.map(item => {
item.quantity = 0;
return item;
});
this.setState({cartItems: newState});
this.state= {
cartItems:[
{name:'item1',price:'$23'},
{name:'item2',price:'$26'},
{name:'item3',price:'$24'},
]
}
updateCartItems=()=>{
var cartItems = this.state.cartItems.map((item)=>{
return {...item,quantity:0}
})
this.setState({cartItems})
}
Related
I am still new to React js.
I am trying to use useState({}) to define an object of objects of orders.
For the newOrderHandler, I am passing the order to be added.
The idea is to add a new object if the order title does not exist and update the amount if the order title already exists in the orders state.
This is the code:
const [orders, setOrders] = useState({});
const newOrderHandler = (newOrder) => {
setOrders(prevOrders => {
console.log('prevOrderss', prevOrders)
// console.log(`prevOrders[newOrder.title]`, prevOrders[newOrder.title])
let newOrders = prevOrders;
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
});
};
The problem here is that although when I log the prevOrders to the console, I get it as I wanted:
However, when I calculate the number of objects in the Navigation component, it just displays 0 always.
This is the code that calculates the number of objects in the Navigation component:
Your Cart <span>{Object.keys(props.orders).length}</span>
This is how I passed the props to the Navigation component:
<Navigation orders={orders} />
This always displays 0. I guess the problem is when defining this: let newOrders in the setOrders function, but I am not sure how to solve it.
Thanks in advance.
The problem is that you React cannot detect that you have changed the object. You need to make a copy, you are passing in the same reference.
newOrders == prevOrders returns true.
What is standard is to make a copy so that you do not mutate the state and react can detect that the object has actually changed.
You can use the spread operator.
let newOrders = { ...prevOrders, [newOrder.title] : { ...prevOrders[newOrder.title] }};
if (newOrders[newOrder.title] == null) {
newOrders[newOrder.title] = newOrder
} else {
newOrders[newOrder.title].amount = +prevOrders[newOrder.title].amount + +newOrder.amount
}
return newOrders;
Spreading the nested property too because you are mutating its amount property. For every level of nesting you will have to use spread for the property you want to change.
I want to retrieve a list of products in relation to the user's position, for this I use Geofirestore and update my Flatlist
When I have my first 10 closest collections, I loop to have each of the sub-collections.
I manage to update my state well, but every time my collection is modified somewhere else, instead of updating my list, it duplicates me the object that has been modified and adds it (updated) at the end of my list and keep the old object in that list too.
For example:
const listListeningEvents = {
A: {Albert, Ducon}
B: {Mickael}
}
Another user modified 'A' and delete 'Ducon', I will get:
const listListeningEvents = {
A: {Albert, Ducon},
B: {Mickael},
A: {Albert}
}
And not:
const listListeningEvents = {
A: {Albert},
B: {Mickael},
}
That's my useEffect:
useEffect(() => {
let geoSubscriber;
let productsSubscriber;
// 1. getting user's location
getUserLocation()
// 2. then calling geoSubscriber to get the 10 nearest collections
.then((location) => geoSubscriber(location.coords))
.catch((e) => {
throw new Error(e.message);
});
//Here
geoSubscriber = async (coords) => {
let nearbyGeocollections = await geocollection
.limit(10)
.near({
center: new firestore.GeoPoint(coords.latitude, coords.longitude),
radius: 50,
})
.get();
// Empty array for loop
let nearbyUsers = [];
// 3. Getting Subcollections by looping onto the 10 collections queried by Geofirestore
productsSubscriber = await nearbyGeocollections.forEach((geo) => {
if (geo.id !== user.uid) {
firestore()
.collection("PRODUCTS")
.doc(geo.id)
.collection("USER_PRODUCTS")
.orderBy("createdDate", "desc")
.onSnapshot((product) => {
// 4. Pushing each result (and I guess the issue is here!)
nearbyUsers.push({
id: product.docs[0].id.toString(),
products: product.docs,
});
});
}
});
setLoading(false);
// 4. Setting my state which will be used within my Flatlist
setListOfProducts(nearbyUsers);
};
return () => {
if (geoSubscriber && productsSubscriber) {
geoSubscriber.remove();
productsSubscriber.remove();
}
};
}, []);
I've been struggling since ages to make this works properly and I'm going crazy.
So I'm dreaming about 2 things :
Be able to update my state without duplicating modified objects.
(Bonus) Find a way to get the 10 next nearest points when I scroll down onto my Flatlist.
In my opinion the problem is with type of nearbyUsers. It is initialized as Array =[] and when you push other object to it just add new item to at the end (array reference).
In this situation Array is not very convenient as to achieve the goal there is a need to check every existing item in the Array and find if you find one with proper id update it.
I think in this situation most convenient will be Map (Map reference). The Map indexes by the key so it is possible to just get particular value without searching it.
I will try to adjust it to presented code (not all lines, just changes):
Change type of object used to map where key is id and value is products:
let nearbyUsersMap = new Map();
Use set method instead of push to update products with particular key:
nearbyUsersMap.set(product.docs[0].id.toString(), product.docs);
Finally covert Map to Array to achieve the same object to use in further code (taken from here):
let nearbyUsers = Array.from(nearbyUsersMap, ([id, products]) => ({ id, products }));
setListOfProducts(nearbyUsers);
This should work, but I do not have any playground to test it. If you get any errors just try to resolve them. I am not very familiar with the geofirestore so I cannot help you more. For sure there are tones of other ways to achieve the goal, however this should work in the presented code and there are just few changes.
I think I'm close to a solution but I need some help,
I have a form object I want to update where key's match to an imported object.
form.title would be set to the value in article.title.
I have done the following, but am struggling how to set this.form[key][value] to this.article[articleKey][articleValue].
Object.entries(this.form).forEach(([key, value]) => {
Object.entries(this.article).forEach(([articleKey, articleValue]) => {
if ([articleKey][0] === [key][0]){
//[value] = [articleValue];
//this.form[key][value]=this.article[articleKey][articleValue]
}
});
Any help would be appreciated, i'm new to javascript. I can't clone the object as i only want to update the data properties in form and bring across all the data in article object.
Response to comment - example of form
form: new Form({
title: '',
description: '',
earliest_date:'',
latest_date:'',
image_file_names:[]
})
Article Example
{"id":21,
"owner_id":1,
"title":"test1",
"description":"Test It",
"earliest_date":"2020-06-01",
"latest_date":"2020-06-06",
"image_file_names":"[\"1593530083background.jpg\",
\"159353008520190713_085629.jpg\"]",
"physical_description":"Test 1"}
This looks to be working, it felt wrong setting value to the key but its not doing that, it is setting value of that key.
Object.entries(this.form).forEach(([key, value]) => {
Object.entries(this.article).forEach(([articleKey, articleValue]) => {
if ([articleKey][0] === [key][0]){
this.form[key] = [articleValue];
}
});
});
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(item){
item.expand = true;
this.setState({})
}
this.state.myArray.map((item) =>{
return <div onClick={()=>this.clickItem(item)}>{item.name}</div>
})
In React, i have a simple array of objects,
when i click on one of theses object, i want to change their prop and update the state, what is the proper way of doing this.
i feel like there could be a better way
You need to copy your state, update the copied state and the set the state.
this.state = {
myArray = [
{
name:"cat",
expand:false
}
]
}
clickItem(key){
let items = this.state.myArray;
items[key].expand = true;
this.setState({items})
}
this.state.myArray.map((key, item) =>{
return <div onClick={()=>this.clickItem(key)}>{item.name}</div>
})
Okay, a couple of things.
You're mutating the state directly which is going to fail silently and you're also missing the key prop on your <div.
This is easily resolved though by using the data you have available to you. I don't know whether each name is unique but you can use that as your key. This helps React decide which DOM elements to actually update when state changes.
To update your item in state, you need a way to find it within the state originally, so if name is unique, you can use Array.prototype.find to update it.
clickItem(item) {
const targetIndex = this.state.items.find(stateItem => stateItem.name === item.name)
if (targetIndex === -1)
// Handle not finding the element
const target = this.state.items[targetIndex]
target.expand = !target.expand // Toggle instead of setting so double clicking works as expected.
this.setState({
items: this.state.items.splice(targetIndex, 1, target) // This replaces 1 item in the target array with the new one.
})
}
This will update state and re-render your app. The code is untested but it should work.
I'm making a primitive quiz app with 3 questions so far, all true or false. In my handleContinue method there is a call to push the users input from a radio form into the userAnswers array. It works fine for the first run of handleContinue, after that it throws an error: Uncaught TypeError: this.state.userAnswers.push is not a function(…)
import React from "react"
export default class Questions extends React.Component {
constructor(props) {
super(props)
this.state = {
questionNumber: 1,
userAnswers: [],
value: ''
}
this.handleContinue = this.handleContinue.bind(this)
this.handleChange = this.handleChange.bind(this)
}
//when Continue button is clicked
handleContinue() {
this.setState({
//this push function throws error on 2nd go round
userAnswers: this.state.userAnswers.push(this.state.value),
questionNumber: this.state.questionNumber + 1
//callback function for synchronicity
}, () => {
if (this.state.questionNumber > 3) {
this.props.changeHeader(this.state.userAnswers.toString())
this.props.unMount()
} else {
this.props.changeHeader("Question " + this.state.questionNumber)
}
})
console.log(this.state.userAnswers)
}
handleChange(event) {
this.setState({
value: event.target.value
})
}
render() {
const questions = [
"Blargh?",
"blah blah blah?",
"how many dogs?"
]
return (
<div class="container-fluid text-center">
<h1>{questions[this.state.questionNumber - 1]}</h1>
<div class="radio">
<label class="radio-inline">
<input type="radio" class="form-control" name="trueFalse" value="true"
onChange={this.handleChange}/>True
</label><br/><br/>
<label class="radio-inline">
<input type="radio" class="form-control" name="trueFalse" value="false"
onChange={this.handleChange}/>False
</label>
<hr/>
<button type="button" class="btn btn-primary"
onClick={this.handleContinue}>Continue</button>
</div>
</div>
)
}
}
Do not modify state directly! In general, try to avoid mutation.
Array.prototype.push() mutates the array in-place. So essentially, when you push to an array inside setState, you mutate the original state by using push. And since push returns the new array length instead of the actual array, you're setting this.state.userAnswers to a numerical value, and this is why you're getting Uncaught TypeError: this.state.userAnswers.push is not a function(…) on the second run, because you can't push to a number.
You need to use Array.prototype.concat() instead. It doesn't mutate the original array, and returns a new array with the new concatenated elements. This is what you want to do inside setState. Your code should look something like this:
this.setState({
userAnswers: this.state.userAnswers.concat(this.state.value),
questionNumber: this.state.questionNumber + 1
}
Array.push does not returns the new array. try using
this.state.userAnswers.concat([this.state.value])
this will return new userAnswers array
References: array push and array concat
You should treat the state object as immutable, however you need to re-create the array so its pointing to a new object, set the new item, then reset the state.
handleContinue() {
var newState = this.state.userAnswers.slice();
newState.push(this.state.value);
this.setState({
//this push function throws error on 2nd go round
userAnswers: newState,
questionNumber: this.state.questionNumber + 1
//callback function for synchronicity
}, () => {
if (this.state.questionNumber > 3) {
this.props.changeHeader(this.state.userAnswers.toString())
this.props.unMount()
} else {
this.props.changeHeader("Question " + this.state.questionNumber)
}
})
console.log(this.state.userAnswers)
}
Another alternative to the above solution is to use .concat(), since its returns a new array itself. Its equivalent to creating a new variable but is a much shorter code.
this.setState({
userAnswers: this.state.userAnswers.concat(this.state.value),
questionNumber: this.state.questionNumber + 1
}
The recommended approach in later React versions is to use an updater function when modifying states to prevent race conditions:
this.setState(prevState => ({
userAnswers: [...prevState.userAnswers, this.state.value]
}));
I have found a solution. This shoud work for splice and others too. Lets say that I have a state which is an array of cars:
this.state = {
cars: ['BMW','AUDI','mercedes']
};
this.addState = this.addState.bind(this);
Now, addState is the methnod that i will use to add new items to my array. This should look like this:
addState(){
let arr = this.state.cars;
arr.push('skoda');
this.setState({cars: arr});
}
I have found this solution thanks to duwalanise. All I had to do was to return the new array in order to push new items. I was facing this kind of issue for a lot of time. I will try more functions to see if it really works for all functions that normally won't. If anyone have a better idea how to achieve this with a cleaner code, please feel free to reply to my post.
The correct way to mutate your state when you want to push something to it is to do the following. Let's say we have defined a state as such:
const [state, setState] = useState([])
Now we want to push the following object into the state array. We use the concat method to achieve this operation as such:
let object = {a: '1', b:'2', c:'3'}
Now to push this object into the state array, you do the following:
setState(state => state.concat(object))
You will see that your state is populated with the object.
The reason why concat works but push doesn't is because of the following
Array.prototype.push() adds an element into original array and returns an integer which is its new array length.
Array.prototype.concat() returns a new array with concatenated element without even touching in original array. It's a shallow copy.