React / javascript addition to array not changing array lenght [duplicate] - javascript

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
I'm learning react native and I'm doing a small app that regists the time of sleep for each day.
I'm using the useEffect() to trigger some modifications of values showed on screen, one of those is the average time average() that I update inside that useEffect:
const [updateAction, setUpdateAction] = useState(false);
useEffect(() => {
console.log("lenght:" + registSleep.length);
var registed = false;
if (!isNaN(enteredHours)) {
for (var i = 0; i < registSleep.length; i++) {
if (
registSleep[i].day === selectedDay &&
registSleep[i].month === selectedMonth &&
registSleep[i].year === selectedYear
) {
registed = true;
registSleep[i].hours = enteredHours;
}
}
if (!registed) {
var newReg = {
day: selectedDay,
month: selectedMonth,
year: selectedYear,
hours: enteredHours,
};
setNewRegist((prevReg) => [
...prevReg,
newReg,
]);
}
if (registSleep.length != 0) {
average();
}
}
console.log("2. lenght:" + registSleep.length);
setviewInfoAction(!viewInfoAction);
}, [updateAction]);
To debug, as you can see I print to console the lenght before I add a new value to the array of regists setNewRegist(...) and as far as I know it should be printing lenght: 0 and then 2. lenght: 1 but instead it prints lenght: 0 and then 2. lenght: 0 and on the next trigger lenght: 1 and then 2. lenght: 1.
Why the array is not updating on addition?

I'm assuming setNewRegist is useState Hook, where it's value is registSleep
const [registSleep, setNewRegist] = useState([ ... ])
Two reasons why it's not working. useState Hook is asynchronous, the logic will not stop for the logic inside setState.
setNewRegist( ... update registSleep)
console.log(registSleep) // will run before setState finishes
However even it did finish in time, registSleep was already set at a fixed value, so it will not change unless the component is rerendered, which is what setState does, to trigger the component to rerender.

//I am considering this
const [ registSleep , setNewRegist ] = useState([])
setNewRegist is async function, so next statment will execute first in your case console.log so it won't have updated registSleep .
So How to check right?
Tada.... !!! You can check each update of registSleep via useEffect like this :
useEffect(() => {
// Here you can get updated length
// as soon as registSleep updates
console.log(registSleep.length);
},[registSleep]) // <-- watch for any update on `registSleep`

Related

How to conveniently use a useState value in a stored function?

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?

Spread operator reflects future operation while in React setState

I'm not sure if this is related to React or just JavaScript.
I'm building a simple voting app. You can add some options and vote +1 for each option.
My App has options as state like below. storedOptions is from localStorage.
function App() {
const [options, setOptions] = useState(
storedOptions ? JSON.parse(storedOptions) : []
);
And handleVote increases count by 1 for the given option.
const handleVote = useCallback((option) => {
setOptions((options) => {
console.log("previous: ", options);
let updatedOptions = [...options];
console.log(updatedOptions); // THIS PART IS STRANGE
const index = updatedOptions.indexOf(option);
console.log(index);
updatedOptions[index] = { ...option, count: option.count + 1 }; // Change reference of the given option only
console.log("new: ", updatedOptions);
updatedOptions = sortByValue(updatedOptions, "count"); // I think this is not related to my problem though, this is why I declared updatedOptions with 'let'. sortByValue function returns new array.
localStorage.setItem(OPTIONS_KEY, JSON.stringify(updatedOptions)); // I'm working with localStorage too, you can ignore this
return updatedOptions;
}, []);
});
But when I voted for an option, it didn't work as it supposed to be. So I logged them out like above and found out that console.log(updatedOptions)(second log) already reflected future operation(increasing count).
Shouldn't count be 0 at that moment? why 1 already?

React - console logs empty an array and then logs a populated array after a second button click

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.

Array not updating when I add new object to it after spreading array first [duplicate]

This question already has answers here:
The useState set method is not reflecting a change immediately
(15 answers)
Closed 2 years ago.
On click of a button I want to update my channels array, and add a new channel but it is not working. Here is the function that is executed when the array is updated.
const channelData = setupChannels(venue !== null ? venue.chatRooms : []);
const [channelName, setChannelName] = useState('');
const [categoryName, setCategoryName] = useState('');
const [channels, setChannels] = useState(channelData);```
const onAddChannelClick = () => {
if (channelName === '') {
return;
}
let existingChannels = [...channels];
let newChannel = {
key: existingChannels.length,
name: channelName,
deletable: true,
markedForDelete: false,
category: categoryName
};
existingChannels.push(newChannel);
console.log(newChannel);
setChannels(existingChannels);
setChannelName('');
setCategoryName('');
setCreateChannel(false);
console.log(existingChannels);
console.log(channels);
};
In the first console log console.log(newChannel); I see my new channel with all the keys and values. When I console.log(existingChannels);I see all the array with my newChannel added to it but all the categories are undefined and when I console.log(channels) my whole newChannel object is not added and all of the categories are undefined.
Can anyone see anything wrong with this code or is the issue most likely coming from somewhere else?
Not clear of why category is undefined. but you can't console channels immediately after setChannels, as the data wouldn't have updated already.
Use useEffect instead to log the data.. or you would find it updated if you are looping them in the return.
useEffect(() => {
console.log(channels)
}, [channels]);

why is useEffect rendering unexpected values?

I am trying to create a scoreboard for a quiz application. After answering a question the index is updated. Here is the code for the component.
export const ScoreBoard = ({ result, index }) => {
const [score, setScore] = useState(0)
const [total, setTotal] = useState(0)
const [rightAns, setRight] = useState(0)
useEffect(() => {
if(result === true ) {
setRight(rightAns + 1)
setTotal(total + 1)
}
if(result === false) {
setTotal(total + 1)
}
setScore(right/total)
}, [index]);
return (
<>
<div>{score}</div>
<div>{rightAns}</div>
<div>{total}</div>
</>
)
}
When it first renders the values are
score = NaN
rightAns = 0
total = 0
After clicking on one of the corrects answers the values update to
score = NaN
rightAns = 1
total = 1
and then finally after one more answer (with a false value) it updates to
score = 1
rightAns = 1
total = 2
Score is no longer NaN but it is still displaying an incorrect value. After those three renders the application begins updating the score to a lagging value.
score = 0.5
rightAns = 2
total = 3
What is going on during the first 3 renders and how do I fix it?
You shouldn't be storing the score in state at all, because it can be calculated based on other states.
All the state change calls are asynchronous and the values of state don't change until a rerender occurs, which means you are still accessing the old values.
export const ScoreBoard = ({ result, index }) => {
const [total, setTotal] = useState(0)
const [rightAns, setRight] = useState(0)
useEffect(() => {
if(result === true ) {
setRight(rightAns + 1)
setTotal(total + 1)
}
if(result === false) {
setTotal(total + 1)
}
}, [index]);
const score = right/total
return (
<>
<div>{score}</div>
<div>{rightAns}</div>
<div>{total}</div>
</>
)
}
Simpler and following the React guidelines about the single "source of truth".
Your problem is that calling setState doesn't change the state immediately - it waits for code to finish and renders the component again with the new state. You rely on total changing when calculating score, so it doesn't work.
There are multiple approaches to solve this problem - in my opinion score shouldn't be state, but a value computed from total and rightAns when you need it.
All of your set... functions are asynchronous and do not update the value immediately. So when you first render, you call setScore(right/total) with right=0 and total=0, so you get NaN as a result for score. All your other problems are related to the same problem of setScore using the wrong values.
One way to solve this problem is to remove score from state and add it to the return like this:
return (
<>
{total > 0 && <div>{right/total}</div>}
<div>{rightAns}</div>
<div>{total}</div>
</>
)
You also can simplify your useEffect:
useEffect(() => {
setTotal(total + 1);
if(result === true ) setRight(rightAns + 1);
}, [index]);
With how you have it set up currently, you'd need to make sure that you are updating result before index. Because it seems like the useEffect is creating a closure around a previous result and will mess up from that. Here's showing that it does work, you just need to make sure that result and index are updated at the right times.
If you don't want to calculate the score every render (i.e. it's an expensive calculation) you can useMemo or useEffect as I have shown in the stackblitz.
https://stackblitz.com/edit/react-fughgt
Although there are many other ways to improve how you work with hooks. One is to make sure to pay attention to the eslint react-hooks/exhaustive-deps rule as it will forcefully show you all the little bugs that can end up happening due to how closures work.
In this instance, you can easily calculate score based on total and rightAns. And total is essentially just index + 1.
I'd also modify the use effect as it is right now to use setState as a callback to get rid of a lot of dependency issues in it:
useEffect(() => {
if (result === true) {
setRight(rightAns => rightAns + 1);
setTotal(total => total + 1);
}
if (result === false) {
setTotal(total => total + 1);
}
}, [index]);
useEffect(()=>{
setScore(rightAns / total ||0);
},[rightAns,total])

Categories