Can I define a variable within setState in React js? - javascript

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.

Related

bad value includes() react

i'm trying to make a function that add a keyword if is not already in the array, if it is shows a error message, but the problem is that the includes() method doesn't work properly, it shows false first (when a word is not in the array), then true (because it is in the array) and then false again if i don't change the word.
I don't know why this is happening but it happens also with indexOf(). Maybe its a react problem with rendering or something like that.
Between if its another way to take an input value and do this, it is welcome
const [repeatedKeyWord, setRepeatedKeyWord] = useState(false)
let keyWords = []
const addKeyword = () => {
let keyword = document.getElementById('keyword').value;
const exist = keyWords.includes(keyword);
console.log(exist)
if (keyword && !exist){
console.log('in')
keyWords.push(keyword)
setRepeatedKeyWord(false)
}
setRepeatedKeyWord(exist? true : false)
console.log(keyWords)
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword} />
You must store your keywords in useState, otherwise you lose the value between re-renders of your component. Thereafter, you can use .includes on your array. React ensures that you'll always have the latest value (e.g. 'snapshot' of your state).
Also note that when you are trying to compute a new state value (i.e. updating your array) you are dependent on the previous value of your state. If that is the case, you can pass a function to the setState function. Have a look at this issue where I have included a link to a working codesandbox for updating previous state values.
As a side note, I would suggest to avoid using let to declare your variables. Only use the let keyword if you are certain that you will re-assign a new value to your variable. Otherwise using const might be better to avoid mistakes.
const [keywords, setKeyWords] = useState([])
const addKeyword = () => {
const keyword = document.getElementById('keyword').value;
return setKeywords((prevState) => {
if (prevState.includes(keyword)) {
return [...prevState]
}
return [...prevState, keyword]
})
}
<PlusIcon className="w-6 text-firstColor cursor-pointer mr-2" onClick={addKeyword}

React display not updating correctly

I'm updating an object within react's state which I use to display a list. The state updates correctly, however the display breaks.
This is the section of the code from inside my render function which produces the list.
this.state.shoppingItems ? this.state.shoppingItems.currentShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
Here is the code that produces the previous items list:
this.state.shoppingItems ? this.state.shoppingItems.previousShoppingItems.map((item, index) => {
console.log(item)
return <ItemSummary key={index} onClickHandler={this.selectItem} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={item} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
}) : undefined}
This is the method which removes the item from the current list and adds it to the previous list, where the issue occurs.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
let shoppingItems = this.state.shoppingItems;
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
Here is the list before I remove the item.
Here is the list after I remove the middle item:
Finally here is the console.log output which shows that the items have updated properly but the display hasn't updated:
I'm entirely new to react coming from angular so I have no idea if this is the correct way to do this or if there is a better way. But could somebody help me figure out why the display isn't updating?
The issue seemed to be the key on the item in the map. I replaced the index with the item's id from the database as below and now it renders properly.
return <ItemSummary key={task._id} updateShoppingItem={this.updateCurrentShoppingItem} shoppingItem={task} removeFromCurrentItems={this.removeFromCurrentItems} addToCurrentList={this.addToCurrentList} />
Similar answer here:
Change to React State doesn't update display in functional component
The issue is the update for shoppingItems. You save a reference to the current state object, mutate it, and store it back in state. Spreading this.state.shoppingItems into a new object first will create a new object reference for react to pick up the change of.
React uses shallow object comparison of previous state and prop values to next state and prop values to compute what needs to be rerendered to the DOM and screen.
removeFromCurrentItems(shoppingItem) {
const items = this.state.shoppingItems.currentShoppingItems.filter(item => item._id !== shoppingItem._id);
const shoppingItems = {...this.state.shoppingItems};
shoppingItems.currentShoppingItems = items;
shoppingItem.number = 0;
shoppingItem.completed = false;
shoppingItems.previousShoppingItems.push(shoppingItem);
this.setState({
shoppingItems: shoppingItems
});
// call to API to update in database
}
I had a similar issue with my application in which I had to delete comments made.
<textarea disabled key={note._id} className="form-control">{note.note}</textarea>
But the issue got resolved when I added the Key attribute to the list item.

Trouble accessing nested array data using React useState

I want to use React's useState hook to store a nested array. The problem is that I can't access individual values from the nested array once I save it. Strangely, I can access the outer arrays, but I can't get individual elements within them. Example from the guitar tab application I'm building:
const TabBar = (props) =>{
const [tabData, setTabData] = useState([]);
const rows=20
let initialTab = []
for (let x=0; x<rows;x++){
initialTab.push(['-','-','-','-','-','-'])
}
useEffect(() => {
setTabData(initialTab)
}, []);
console.log(tabData)//this works
console.log(tabData[0]) //this works
console.log(tabData[0][0]) //this gives me 'Uncaught TypeError: Cannot read property '0' of undefined'
This is most likely because the it's trying to access that index before the effect runs and fills the array. Without knowing more context around the component you're writing, it looks like you want the array to initially be filled with those values, therefore I would suggest setting that as your default state. For example:
const defaultState = new Array(20).fill(["-", "-", "-", "-", "-", "-"]);
export default function App() {
const [tabData, setTabData] = React.useState(defaultState);
console.log(tabData); //this works
console.log(tabData[0]); //this works
console.log(tabData[0][0]);
return (
<div>anything</div>
);
}
Then call setTabData whenever you need to make updates to this array.

How to change the property of an object inside an array propertly in react

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.

How to replace the value of an array of objects in React

I have this function that returns an array of objects, every object represent a sticky, what I want is to change the value of "content" everytime I click one of the stickies
handleStickyEdition = (index) => {
const { currentStage } = this.props
const stickies = currentStage.get('stickies')
const updatedStickies = [...stickies]
console.log(updatedStickies)
}
And the result of calling the console.log is this array of objects:
If I do console.log(updatedStickies[index].get('content')) I will get the content of the object I want to change. For example name 3.
How can I replace this content with an empty string? in other words, if I click the object in the position 0, how can I make name 3 equals to ''
I would suggest using a map like so.
this.setState({
updatedStickies: this.state.updatedStickes.map(sticky => ({
...sticky
content: sticky.id === idOfStickyWeWantToUpdate ? "" : "content"
}))
});
I see you are reading stickies from props, I would suggest having a function in your parent component to run the above code which you can call from your child component if need be.

Categories