I am trying to use hooks to add numbers from an array together. Currently it's a voting system. The result of adding all the numbers using a map statement gives me 0. I am pretty sure this has to do with useState not updating in time to add the numbers, therefore it's always giving me zero. I know I could put them in a separate array, and add that, but that seems a little verbose for something that would seem so simple.
Here is the code I have that produces 0
const PollResultsContainer = (props) => {
const option = props.option
const [totalVotes, setTotalVotes] = useState(0)
useEffect(() => {
let newVote
if (option.length > 0) {
option.map(opt => {
newVote = opt.optionVotes + totalVotes
})
}
setTotalVotes(newVote)
}, [totalVotes])
console.log(totalVotes)
return (
<>
<div className='poll-results-div'>
<TitleCardNS title={`${totalVotes}`} size='2.5rem' align='center' color='white' />
</div>
There is not need to store it in a state.
const PollResultsContainer = ({option}) => {
let totalVotes = option.reduce((acc, {optionVotes}) => acc + optionVotes, 0);
console.log(totalVotes);
};
I don't think you need any state or effect in this component :
const PollResultsContainer = (props) => {
const option = props.option
let totalVotes = 0;
if (option.length > 0) {
option.forEach(opt => {
totalVotes += opt.optionVotes
})
}
console.log(totalVotes)
I added another variable into the equation. This probably solved the problem of useState not updating in time.
const PollResultsContainer = (props) => {
const option = props.option
const [totalVotes, setTotalVotes] = useState(0)
let newVote = 0
useEffect(() => {
if (option.length > 0) {
option.map(opt => {
newVote = opt.optionVotes + newVote
console.log(newVote)
})
}
console.log(newVote)
setTotalVotes(newVote)
}, [totalVotes])
console.log(totalVotes)
Related
I try to insert a string into a particular index of object if condition is true inside a forloop but its not inserting of some reason. I tried to use push and append and splice but splice just inserting entire string as an new object into the array and i need it to just append to existing object. Any ideas how to make it work?
Data looks like that:
const [concerts, setConcerts] = useState([]);
const [tickets, setTickets] = useState([]);
const [limit, setLimit] = useState(25);
const navigate = useNavigate();
const [button, setButton] = useState(false);
const [array, setArray] = useState([]);
//Raw JSON Date example: "2023-02-08T23:15:30.000Z"
let currentDate = new Date().toJSON().slice(0, 10);
const json = { available: "true" };
useEffect(() => {
const loadConcerts = async () => {
const resConcerts = await axios.get("/data/concerts");
const resTickets = await axios.get("/data/tickets");
let table = [];
setTickets(resTickets.data);
// getting all concerts above today
const filteredData = resConcerts.data.filter((concert) => {
return concert.datum >= currentDate;
});
filteredData.forEach((element) => {
table.push(element);
// table.splice(10, 0, { status: "available" });
});
setArray(table);
for (let i = 0; i < resTickets.data.length; i++) {
for (let j = 0; j < filteredData.length; j++) {
if (
resTickets.data[i].concertid == filteredData[j].id &&
resTickets.data[i].booked == 0
) {
table.push({ status: "avaiable" });
// table.splice(10, 0, { status: "available" });
}
}
}
setArray(table);
// filteredData.forEach((concert) => {
// for (const ticket of tickets) {
// if (concert.id == ticket.concertid && ticket.booked == 0) {
// table.push(json);
// }
// }
// });
setConcerts(
filteredData.sort((a, b) =>
a.datum > b.datum ? 1 : a.datum < b.datum ? -1 : 0
)
);
};
console.log("from use effect: " + array.length);
loadConcerts();
}, []);
After using splice method:
Update
Problem is solved. I used Object.assign() helped to append string to existing object in array. Actually i had to insert another object, not a single variable.
The problem is you are trying to push a string "available" into an array-of-objects.
Here you see the object with a property datum:
const filteredData = resConcerts.data.filter((concert) => {
return concert.datum >= currentDate;
});
Yet below when you push, you are not pushing an object into the array which is problematic. It should probably be something like this but you have to verify:
Instead of this:
filteredData.push("available");
Domething like this:
filteredData.push({ datum: '', status: 'available' );
I don't know what your data object is but it's an object not a string you need to add to that array.
The looping twice is likely from React 18 New Strict Mode Behaviors. It intentionally unmounts/remounts components to fire your useEffect calls twice - so that you can identify problematic side effects. If you remove <StrictMode> or run in production that double-looping should not occur.
Problem solved. Push() neither splice() method didn't helped. What helped me to append my object to another object without changing the data was Object.assign() function
for (let i = 0; i < resTickets.data.length; i++) {
for (let j = 0; j < filteredData.length; j++) {
if (
resTickets.data[i].concertid == filteredData[j].id &&
resTickets.data[i].booked == 0
) {
Object.assign(filteredData[j], obj);
}
}
}
const [lnames, setlNames] = React.useState();
const [lnums, setlNums] = React.useState();
React.useEffect(() => {
axios.get("http://localhost:7001/lunch").then(response => {
let arr1 = [];
let arr2 = [];
response.data.forEach(c => {
arr1.push(c.table_id);
arr2.push(c.table_num_count);
});
setlNames(arr1);
setlNums(arr2);
});
}, []);
const [dnums, setdNums] = React.useState();
React.useEffect(() => {
axios.get("http://localhost:7001/dinner").then(response => {
let arr1 = [];
response.data.forEach(c => {
arr1.push(c.table_num_count);
});
setdNums(arr1);
});
}, []);
const [bnums, setbNums] = React.useState();
React.useEffect(() => {
axios.get("http://localhost:7001/breakfast").then(response => {
let arr1 = [];
response.data.forEach(c => {
arr1.push(c.table_num_count);
});
setbNums(arr1);
});
}, []);
const customer_count_breakfast = bnums;
const customer_count_lunch = lnums;
const customer_count_dinner = dnums;
let sumArray = []
if (lnums & lnums.length > 0) {
//sumArray = lnums.map((l, i) => l + bnums[i] + dnums[i]);
for (let i = 0; i < lnums.length; i++) {
sumArray[i] = bnums[i] + lnums[i] + dnums[i];
}
}
// IF COMMENT ABOVE AND UNCOMMENT HERE THE ERROR IS GONE BUT sumArray is not computed
// if (lnums && lnums[0]) {
// //sumArray = lnums.map((l, i) => l + bnums[i] + dnums[i]);
// for (let i = 0; i < lnums.length; i++) {
// sumArray[i] = bnums[i] + lnums[i] + dnums[i];
// }
// }
Hello I have the above code giving me the following error at the if loop condition : Uncaught TypeError: Cannot read properties of undefined (reading '0'). I think I know why the error is being displayed. It is indicating that I am trying to access a property or index of a variable that is undefined, this is most likely due to the useEffect, where when I call for the if condition the value is not set yet, so it is undefined and thus does not have a length. When I try to comment the first if condition and uncomment the second, I get no more errors but the summation does not work, it returns an empty array.
How would I be able to sum the values of .useState() variables in this case?
You need to look into react lifecycle and useMemo.
read about lifecycle: https://www.w3schools.com/react/react_lifecycle.asp
read about useMemo: https://www.w3schools.com/react/react_usememo.asp
Here is a quick example where useEffect puts numbers into an array, useMemo has dependency on those arrays and adds all the numbers together.
import { useEffect, useState, useMemo } from "react";
export default function App() {
const [arr1, setArr1] = useState([]);
const [arr2, setArr2] = useState([]);
// called everytime arr1 or arr2 changes. Which causes react to rerender.
// first run sum = 0
const sum = useMemo(() => {
let res = 0;
if (arr1.length && arr2.length) {
for (let i = 0; i < arr1.length; i++) {
res += arr1[i] + arr2[i];
}
}
return res;
}, [arr1, arr2]);
// called once, updated setArr1 causing sum to be called
useEffect(() => {
setArr1([1,2,3]);
}, []);
// called once, updated setArr2 causing sum to be called
useEffect(() => {
setArr2([4,5,6]);
}, []);
return (
<div>
<p> sum: {sum} </p>
</div>
);
}
Essentially In your code you are calling the arrays before useEffect has finished setting their values.
I solved the issue by adding a try-catch statement, not sure if it is feasible at all
try {
for (let i = 0; i < lnums.length; i++) {
sumArray[i] = bnums[i] + lnums[i] + dnums[i];
}
console.log("Statement was successful");
} catch (error) {
}
Hi stackoverflow people I know this has been asked about a million times. However I don't think I have found an answer to my problem!
Long story short I am updating a state in my react native app only if a statement is true. What I am experiencing is this state not being updated somehow.
const emptyArray = []
const [collapsed, setCollapsed] = useState(true);
const [selectedGem, setSelectedGem] = useState(false);
const [usedGem, setUsedGem] = useState(emptyArray);
const [selectedPropType, setSelectedPropType] = useState('all');
const randomItem = (items) => {
return items[Math.floor(Math.random()*items.length)];
}
const getRandomQuestion = () => {
let selectableGems = gems
const maxGems = selectableGems.length;
let tempGems = [];
if(usedGem.length >= maxGems) {
setUsedGem(emptyArray)
}
for (let i=0; i<maxGems; i++)
{
if(usedGem.length === 0 || usedGem.find(gem => gem === i) === undefined) {
tempGems.push(i)
}
}
const randomGemNumber = randomItem(tempGems)
setUsedGem([...usedGem, randomGemNumber])
setSelectedGem(selectableGems[randomGemNumber])
}
Basically if(usedGem.length >= maxGems) {happens only when all the gems have been randomised once (there are 130 item in the gems variable). I'd expect then the usedGem to be empty in the for but this isn't the case.
I have tried a few solutions I found without luck. I know this has to do with async but I can't figure it out! Anyone could help me please?
Here this might help. The thing is you can't get instant update after setting state
const getRandomQuestion = () => {
let selectableGems = gems
const maxGems = selectableGems.length;
let tempGems = [];
let gems = Array.from(usedGem);
if(gems.length >= maxGems) {
gems = []
}
for (let i=0; i<maxGems; i++)
{
if(gems.length === 0 || gems.find(gem => gem === i) === undefined) {
tempGems.push(i)
}
}
const randomGemNumber = randomItem(tempGems)
setUsedGem([...gems, randomGemNumber])
setSelectedGem(selectableGems[randomGemNumber])
}
I am learning React and am writing a React App that lets you press buttons that have values (1, 0, -1) and then does some calculations (average, %pos, etc).
I have written a function to calculate the average in my App.js component
const [good, setGood] = useState(0)
const [neutral, setNeutral] = useState(0)
const [bad, setBad] = useState(0)
const [allNum, setAll] = useState([])
const [average, setAverage] = useState(0)
const calcAverage = () => {
console.log('values in calcAvg for allNum: ', allNum)
let total = 0;
for(let i = 0; i < allNum.length; i++) {
total += allNum[i];
}
return total/allNum.length;
}
const handleGoodClick = () => {
setGood(good + 1)
setAll(allNum.concat(1))
setAverage(calcAverage());
}
const handleNeutralClick = () => {
...
}
const handleBadClick = () => {
...
}
return(
<div>
<h1>Give Feedback</h1>
<Button handleClick={handleGoodClick} text="good"/>
...
<Statistics good={good} neutral={neutral} bad={bad} allNum={allNum} average={average}/>
</div>
)
}
The Statistics component is as follows:
const Statistics = (props) => {
console.log('props value is: ', props)
return(
<div>
<h1>Statistics</h1>
...
<Statistic text="Average" value={props.average}/>
</div>
)
}
When I press a button and the app attempts to calculate the average, the average array is always 1 value behind.
ie, open app, press good, Average shows as NaN, console.log shows the allNum array containing a 1 but when Average is calculated that 1 is not contained in the allNum array yet. Why doesn't it go in order? How can I make it execute in order? What is the best practice approach?
Thanks
The reason the calculations receive the previous values is because the setState functions provided by useState do not set the state synchronously.
To overcome this you can use something like a useEffect to update average whenever allNum changes (reference comparison).
const MiscComponent = () => {
const [good, setGood] = useState(0)
const [neutral, setNeutral] = useState(0)
const [bad, setBad] = useState(0)
const [allNum, setAll] = useState([])
const [average, setAverage] = useState(0)
// The useEffect will trigger based on what is entered into the
// dependency array: [setAverage, allNum]
// Each render cycle the values will be compared (reference comparison)
// to the previous values in the dependency array and if there is a
// change the effect will be run.
// NOTE: There is always considered to be a "change" on component mount.
// setAverage is guarenteed to never change reference once created by useState.
// Someone else probably has the link to the React docs for this statement.
// So everytime allNum is updated this effect should run.
useEffect(() => {
const average = allNum.reduce((a, b) => a + b, 0) / allNum.length;
setAverage(average)
}, [setAverage, allNum])
// Since the average update will be handled by the useEffect it can
// now be removed from the click handler.
const handleGoodClick = () => {
setGood(good + 1)
setAll(allNum.concat(1))
}
const handleNeutralClick = () => {
/* Code here */
}
const handleBadClick = () => {
/* Code here */
}
return(
<div>
<h1>Give Feedback</h1>
<Button handleClick={handleGoodClick} text="good"/>
{/* Components Here */}
<Statistics good={good} neutral={neutral} bad={bad} allNum={allNum} average={average}/>
</div>
)
}
Another option if average is only ever effected (affected? whatever) by other state variables you could use a useMemo instead.
const MiscComponent = () => {
const [good, setGood] = useState(0)
const [neutral, setNeutral] = useState(0)
const [bad, setBad] = useState(0)
const [allNum, setAll] = useState([])
// useMemo will return a memoized value for average that will only be recalculated
// based on its associated dependency array.
const average = useMemo(() => {
const average = allNum.reduce((a, b) => a + b, 0) / allNum.length;
return average;
}, [allNum]);
// Since the average update will be handled by the useMemo it can
// now be removed from the click handler.
const handleGoodClick = () => {
setGood(good + 1)
setAll(allNum.concat(1))
}
const handleNeutralClick = () => {
/* Code here */
}
const handleBadClick = () => {
/* Code here */
}
return(
<div>
<h1>Give Feedback</h1>
<Button handleClick={handleGoodClick} text="good"/>
{/* Components Here */}
<Statistics good={good} neutral={neutral} bad={bad} allNum={allNum} average={average}/>
</div>
)
}
I have a problem with changing state while splicing element from another array then changes in state. TO be clear, i don't want state to change, just want to splice one element from array arrayWithFilters = []
export const SHOW_RESULTS_WHEN_NOTHING = (state) => {
let selectedFilters = {...state.selected.filters},
arrayWithFilters = []; //Making splice from this array, but it also removes from state.selected.filters.
for (let filter in selectedFilters) {
arrayWithFilters.push(selectedFilters[filter])
}
arrayWithFilters.forEach((filter) => {
if (filter.values.includes('qcontrast')) {
filter.values.splice('qcolor', 1);
console.log(filter)
}
})
}
problem solved with the next code
export const SHOW_RESULTS_WHEN_NOTHING = (state) => {
let selectedFilters = {...state.selected.filters},
arrayWithFilters = [];
let selectedFiltersCopy = JSON.parse(JSON.stringify(selectedFilters));
arrayWithFilters = Object.values(selectedFiltersCopy);
arrayWithFilters.forEach((filter) => {
if (filter.values.includes('qcontrast')) {
filter.values.splice('qcontrast', 1);
}
})
console.log(arrayWithFilters)
}