I have a component that stores an array of items in a useState which are later displayed. This allows me to update the list and rerender it. I'm trying to create functions I can store and send to other components to allow them to sort my list and have the result displayed. Unfortunately, when I create and store a function, it only uses the initial value of the useState.
For example, look at my code below:
export default function MyWindow ({someAccessor}) {
const [objectList, setObjectList] = useState([])
const sortObjects = () => {
let newList = [...objectList]
newList.sort(someSortFunction)
setObjectList(newList)
}
const createAndSortObjects = () => {
let newList = [1, 2, 3, etc.]
newList.sort(someSortFunction)
setObjectList(newList)
}
useEffect(() => {
populateObjectListFunction() //Line 1
someAccessor.passFunction(sortObjects) //Line 2
someAccessor.passFunction(createAndSortObjects ) //Line 3
}, [])
return (
<div>
{objectList.mapTo(someComponentMap)}
</div>
)
}
In Line 3, if the createAndSortObjects funtion is called by the accessor, it is able to create a new array, sort it as needed, and then update my objectList variable. If I try to do the same with Line 2, however, it only uses the inital value of objectList, which is [], and replaces the array populated in Line 3.
How can I conveniently fix this issue, and have Line 2 update the existing item? I think I could probably use a useRef and access the .current value in sortObjects, but this would mean I need two separate variables to keep track of one object. I also can't switch from my useState because then the components won't get rerendered when the list changes. What should I do?
Related
I am running into a slight problem with using React and its hooks. I am trying to print out an array from an API, but it first prints as an empty array to the console, and then only when I click the button again does it prints the array.
Here is the function I'm using to make the array from API data:
const getChampion = () => {
for(let i = 0; i < getData.length; i++){
let individualChamp = champPeep.current.value;
if(getData[i].Name === individualChamp){
// console.log(getData[i]);
setShowChampion(individualChamp);
setChampionTitle(getData[i].Title);
let hitPoints = getData[i].Hp
let attack = getData[i].Attack;
let defense = getData[i].Defense;
let attackRange = getData[i].AttackRange;
let armor = getData[i].Armor;
setRadarData([hitPoints, attack, defense, attackRange, armor]);
console.log(radarData) //returns empty array don't know why
}
} //! Have to click search twice to update array to new array
} //Get Champion name and check to see if it is found in the API
Here is the button the input field that I assigned to this function:
return(
<div>
<div className='search'>
<input ref={champPeep} type="search" id='champion-search' placeholder='e.g Annie' />
</div>
<button onClick={getChampion} className='btn-prim'>Search</button>
</div>
)
And this is what is being logged to the console when I click on button btn-prim:
[]
And when I click the btn-prim button again this is then logged (which is correct):
(5) [524, 2, 3, 625, 19]
Is there something I'm doing wrong?
setState is asynchronous in react, so when you try to log radarData immediately after setRadarData it displays previous data stored in radarData. You can use useEffect hook to log current radarData state
useEffect(() => {
console.log(radarData)
}, [radarData])
why React setStates are async : Why is setState in reactjs Async instead of Sync?
I suggest that instead of you using
console.log(radarData) //returns empty array don't know why
try to add the useEffect hook to log the value of radarData whenever it changed.
Use something like:
useEffect(() => {console.log(radarData)}, [radarData])
State updates will reflect in their next rerender and not immediately. This has already been solved.
Basically your
setRadarData([hitPoints, attack, defense, attackRange, armor]);
console.log(radarData) //returns empty array because its still using the default state {}.
Refer to The useState set method is not reflecting a change immediately.
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 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.
I have two functions and when I press each of them I create an array.
this.state = {
food:[],
sports:[],
interest:[],
}
_favoriteFood(foodState){
const food = foodState
this.setState({food:food})
console.log(food)
console.log(this.state.food)
}
_favoriteSports(SportsState){
const sports = SportsState
this.setState({sports:sports})
console.log(sports)
console.log(this.state.sports)
}
render(){
return (
<View>
<FavoriteFood favoriteFood={this._favoriteFood}/>
</View>
<View>
<FavoriteSports favoriteSports={this._favoriteSports}/>
</View>
)}
So for example, I am getting arrays like food:[pizza, hodog] and sports:[basketball, surfing] when I call a method by pressing a button.
My question is when I try to merge two arrays like:
const interest = [...this.state.food, ...this.state.sports]
Its showing undefined because I think I am calling it before the render happens.
Should I make another method to merge arrays?
Any advice or comments would be really helpful. Thanks in advance :)
You problem can happen because React doesn't change the state immediately when you call setState, it may change the state later. If you want to access the state after React applies the change, you should use the second argument of the setState method:
_favoriteFood(food){
this.setState({food}, () => {
const interest = [...this.state.food, ...this.state.sports];
});
}
Reference
The other solution is to use your method argument instead of reading the same value from this.state:
_favoriteFood(food){
this.setState({food});
const interest = [...food, ...this.state.sports];
}
BTW, you should not store const interest = [...this.state.food, ...this.state.sports] in the state, because it can be derived from the other state variables.
Push multi item in array
Merge Two Array
var subject1=['item1','item2']
var subject=['item3','item4','item5'];
subject1.push(...subject)
console.log(subject1);
//output
['item1', 'item2', 'item3', 'item4', 'item5']
I am using React with Redux for my current project.
I have a state object like so:
state: {
reducerOne: {
keyOne: valueOne,
keyTwo: valueTwo
}
reducerTwo: {
keyThree: valueThree
keyFour: valueFour
}
}
Suppose valueFour is an array of objects. I use the valueFour array to map a bunch of React elements like so in the render method:
this.props.valueFour.map(()=> <div/>)
(Obviously the above is simplified but all that I am trying to indicate is that I am rendering a bunch of elements)
Now, I would like to update only 1 of the above elements. In my action/reducer code I return the entire valueFour array with the 1 element updated in a function like so:
action code
export function updateThingInArray(elementId){
return (dispatch, getState) => {
const myArray = getState().reducerTwo.keyFour
for (let i=0; i < myArray.length; i++) {
if (myArray[i].id === elementId){
const result= [
...myArray.slice(0,i),
Object.assign({},
myArray[i],
{field:newValue}),
...myArray.slice(i+1)
]
dispatch(update(result))
}
}
}
}
reducer code:
export default handleActions({
[UPDATE]: (state,{payload}) => Object.assign({},state, {keyFour: payload})
},{
keyThree:{},
keyFour:{}
})
(My reducer code uses the library redux-actions)
I hope all this makes sense. If it doesn't please ask and I will clarify.
But my question is, since keyFour has the entireArray technically updated, does that mean ALL my elements will update even though only 1 has new data? If so, is there a more efficient way of implementing what I am currently doing?
The render method will be called again, which means it will map through that array again. You can look at the elements tab in chrome and you can see the html that flashes, it is the part that changed. If the component has the same data and the same key, the actual html won't be changed, but the render method will be called in the children.
Edit: didn't realize you were updating a nested key, React only checks shallow equality: https://facebook.github.io/react/docs/shallow-compare.html