setState not updating state after useEffect - reactjs

I'm making a shopping cart app and I have a variable that loops through all prices and gets a total price of the cart. I am trying to have that total amount update the totals state through setTotals but while the total variable itself updates as items are removed from the cart, calling setTotals(total) does not update the state. It stays with the initial state on render (total price of all prices in cart).
my state declaration:
const [totals, setTotals] = useState()
getting the total from prices array:
var total = 0
for (var i = 0; i < prices.length; i++) {
total += prices[i];
}
calling setTotals in useEffect:
useEffect(() => {
setTotals(total) <-- From the loop above
setCartCopy([...deepCartCopy])
}, [totals])
Can anyone please help?

I think your approach of a card code is not the good one.
Here you always have the same information twice: you're getting the total in total and then you're setting totals with that same result. You can simplify by keeping only one of the two variables.
Also your code here is not working because the useEffect will never be executed: As you have put totals as a dependency, but it never changes. Even if you change totals somwhere else, you will have an infinite loop because in the useEffect depending on totals's changes, it change it value, which will execute the useEffect that changes totals etc etc, infinite loop.
I think your loop should also be in a useEffect with no dependencies as it will be executed only at the first render.
I would do soemthing like that, and maybe the code can be move improved:
const [prices, setPrices] = useState([1, 3, 6, 39, 5]);
const [total, setTotal] = useState(0);
useEffect(() => {
// Sets total with already present products in the card
// This useEffect is called only at the first render
prices.forEach((price) => {
setTotal((total) => total + price);
});
}, []);
useEffect(() => {
setTotal((total) => total + prices[prices.length - 1]);
// Called when prices values changes, like if you're adding a product to your card
// It keeps your total updated
}, [prices]);
const addPrice = () => {
// Simulate a new product in your card
setPrices((prices) => [...prices, Math.floor(Math.random() * 10)]);
};
return (
<div className="App">
<p>total: {total}</p>
<button onClick={() => addPrice()}>add price</button>
</div>
);
I hope I'm clear in my answer lol

const [finalTotal, setFinalTotal] = useState(0);
let total = 0;
for (let i = 0; i < prices.length; i++) {
total += prices[i];
}
useEffect(() => {
setFinalTotal(total);
}, [total, setFinalTotal]);
https://codesandbox.io/s/stackoverflowhelp-i70fd?file=/src/App.js

Related

Proper on implementing incremental values

Now I know the title may be a bit vague so let me help you by explaining my current situation:
I have an array worth of 100 object, which in turn contain a number between 0 and 1. I want to loop through the array and calculate the total amount e.g (1 + 1 = 2).
Currently using .map to go through every object and calaculate the total. When I am counting up using the useState hook, it kinda works. My other approach was using a Let variabele and counting up like this. Although this is way to heavy for the browser.
I want to render the number in between the counts.
const[acousticness, setAcousticness] = useState(0);
let ids = [];
ids.length == 0 && tracks.items.map((track) => {
ids.push(track.track.id);
});
getAudioFeatures(ids).then((results) => {
results.map((item) => {
setAcousticness(acousticness + item.acousticness)
})
})
return (
<div>
Mood variabele: {acousticness}
</div>
)
What is the proper way on doing this?
I think this is roughly what you are after:
import {useMemo, useEffect, useState} from 'react';
const MiscComponent = ({ tracks }) => {
// Create state variable / setter to store acousticness
const [acousticness, setAcousticness] = useState(0);
// Get list of ids from tracks, use `useMemo` so that it does not recalculate the
// set of ids on every render and instead only updates when `tracks` reference
// changes.
const ids = useMemo(() => {
// map to list of ids or empty array if `tracks` or `tracks.items` is undefined
// or null.
return tracks?.items?.map(x => x.track.id) ?? [];
}, [tracks]);
// load audio features within a `useEffect` to ensure data is only retrieved when
// the reference of `ids` is changed (and not on every render).
useEffect(() => {
// create function to use async/await instead of promise syntax (preference)
const loadData = async () => {
// get data from async function (api call, etc).
const result = await getAudioFeatures(ids);
// calculate sum of acousticness and assign to state variable.
setAcousticness(result?.reduce((a, b) => a + (b?.acousticness ?? 0), 0) ?? 0)
};
// run async function.
loadData();
}, [ids, setAcousticness])
// render view.
return (
<div>
Mood variabele: {acousticness}
</div>
)
}

React(Next.js) Set Timeout and useState not doing as should

So I basically want a tag that looks like this.
This is tyty<span> {textArray[Index]}</span>
and I want textArray[Index] to show a different value every 3 seconds. Currently my code cycles through the array, however, it seems to gofrom Array 0 to Array 2 then Array 4.
I want it to Show array 0 then 3 seconds later array 1, 3 secs, array 2, 3 secs, array 3, then back to array 0 and repeat.
My code looks like this:
const textArray = [
"digital",
"development",
"graphics",
"blog"
]
const [Index, setIndex] = useState(0);
const intervalID = setTimeout(changeText, 3000);
function changeText(){
if(Index >= 0 && Index < 4) {
setIndex((prevIndex) => ++prevIndex);
}
if(Index > 3){
setIndex((prevIndex) => prevIndex=0);
}
}
Why can't I also seem to get it to go through array 1 and array 3.
When I go to google dev mode, I type in Index into the console and it says its not defined. Same with textArray.
First - memoize your array or other objects using useState or useMemo. Without that - objects and arrays will be recreated on each render. And if for primitives like boolean, numbers, strings it is +- ok and depsArrays of useMemo and useEffect will handle them right - for arrays and object it will not be ok due to even if object is the same in terms of values - reference to this object will be changed (new object created) and that will cause pretty bad behavior and tons of unneded re-renders, especially if you pass those objects and arrays down to the child components.
Next - it is setInterval, not setTimeout.
Last - always clear Intervals you created.
import { useState, useEffect, useMemo } from "react";
export default function App() {
// memoize the array and dont recreate it on each render
const textArray = useMemo(
() => ["digital", "development", "graphics", "blog"],
[]
);
const [index, setIndex] = useState(0);
// Will be recalculated when index or textArray changed
const text = useMemo(() => {
return textArray[index];
}, [index, textArray]);
useEffect(() => {
// setInterval, not the setTimeout
const intervalId = setInterval(() => {
// Index will go up but % will cut it down
setIndex((prev) => (prev + 1) % textArray.length);
}, 3000);
return () => clearInterval(intervalId);
}, [textArray]);
return (
<div className="App">
This is{" "}
<a href="https://nextjs.org">
tyty<span> {text}</span>
</a>
</div>
);
}

Why is the setInterval function not working properly?

It keeps showing the error message that it is an infinite loop. I am only beggining to learn React, and this is a Clicker game. How do I change my code to make the setInterval work. Thank you.(BTW I do not want any other changes to the code that won't affect the setInterval function. And yes, I have used setInterval in many projects already and it worked out fine.)
import "./styles.css";
export default function App() {
let [num, setNum] = useState(0);
let [add, setAdd] = useState(1);
let [numC, setNumC] = useState(0);
let [numCP, setNumCP] = useState(10);
let [numW, setNumW] = useState(0);
let [numWP, setNumWP] = useState(20)
setInterval(setNum(num+=numW),3000);
const click = () => {
setNum((num += add));
};
const clicker = () => {
if (num >= numCP) {
setNumC((numC += 1));
setNum((num -= numCP));
setNumCP((numCP += 5));
setAdd((add += 1));
}
};
const worker = () => {
if (num >= numWP) {
setNumW((numW += 1));
setNum((num -= numWP));
setNumWP((numWP += 10));
}
};
return (
<div className="App">
<h1>Clicker Game</h1>
<div>
{num}
<button onClick={click}>Click</button>
</div>
<p />
<div>
{numC}
<button onClick={clicker}>Buy({numCP})</button>
</div>
<div>
{numW}
<button onClick={worker}>Buy({numWP})</button>
</div>
</div>
);
}```
There are a couple of issues.
First you are immediately calling the setNum when you should be passing a callback to be executed when the interval is passed.
So setInterval(() => setNum(num+=numW),3000);
But now you have the second issue, each time the component is re-rendered you will initiate an additional interval. (and it will be re-rendered a lot, at the minimum each time the interval callback is fired)
So you would likely need to use a useEffect, with 0 dependencies so it runs once, if you want to set it and let it run continuously.
useEffect(() => {
setInterval(() => setNum(num += numW), 3000);
}, []);
But now you will encounter yet another issue. The num and numW used in the interval will be locked to the values in the first render of the component.
For the num you can work around it, by using the callback syntax of the setNum
useEffect(() => {
setInterval(() => setNum(currentNum => currentNum += numW), 3000);
}, []);
but numW will never update.
A final tool, is to reset the interval each time the numW or num changes. To do that you will need to return a function from the useEffect that does the clearing.
useEffect(() => {
const interval = setInterval(() => setNum(currentNum => currentNum += numW), 3000);
return () => clearInterval(interval);
}, [numW]);
But this will have the minor issue that the interval is now not constant, since it resets.
Every time one of your state variables changes, the component is re-rendered, i.e. the function App is called again. This will call setInterval over and over again.
You want to look at useEffect to put your setIntervals in.

React Incremeting Counter Behind

I've just learned to use UseState to create a simple incremental counter, but I've noticed some odd behavior with the count being 2 numbers behind (+ - ) in console.log. Now on the screen the number displays fine, but this creates an issue because I'm trying to change the color of the number if it's negative or positive.
Because I'm trying to change the display color of the number on the screen, would UseEffect be a good solution to this problem? I'm going to go back and watch some YT videos on UseEffect, but figured I'd ask here as well. I was thrilled when I was able to figure out how to change the classnames using state, but then got a pie in the face when the numbers weren't changing colors correctly.
Here's an example of the behavior I'm seeing.
const { useState } = React
function Vote () {
const [count, setCount] = useState(0)
const [color, setColor] = useState('black')
function handleDecrement () {
setCount(count - 1)
checkCount()
}
function handleIncrement () {
setCount(count + 1)
checkCount();
}
function checkCount () {
// Less than 0 make it red
if (count < 0) {
setColor('red')
console.log(count)
// Greater than 1 make it green
} else if (count > 0 ) {
setColor('green')
console.log(count)
// If it's 0 just keep it black
} else {
setColor('black')
console.log(count)
}
};
return (
<div>
<button onClick={handleDecrement}>-</button>
<h1 className={color}>{count}</h1>
<button onClick={handleIncrement}>+</button>
</div>
)
}
ReactDOM.render(<Vote />, document.getElementById('root'))
Yes, you can simply use an effect hook with dependency to check the color. When count updates the effect hook callback is triggered.
The issue is that react state updates are asynchronous, so the updated state count won't be available until the next render cycle; you are simply using the count value from the current render cycle.
Note: When incrementing/decrementing counts you should use a functional state update. This ensures state is correctly updated from the previous state in the case multiple state updates are enqueued within any single render cycle.
function Vote() {
const [count, setCount] = useState(0);
const [color, setColor] = useState("black");
function handleDecrement() {
setCount(count => count - 1);
}
function handleIncrement() {
setCount(count => count + 1);
}
useEffect(checkCount, [count]);
function checkCount() {
// Less than 0 make it red
if (count < 0) {
setColor("red");
console.log(count);
// Greater than 1 make it green
} else if (count > 0) {
setColor("green");
console.log(count);
// If it's 0 just keep it black
} else {
setColor("black");
console.log(count);
}
}
return (
<div>
<button onClick={handleDecrement}>-</button>
<h1 className={color}>{count}</h1>
<button onClick={handleIncrement}>+</button>
</div>
);
}
When updating the state based on the current state always use the callback version of setState which receives the current state as an argument and should return the next state. React batches state updates and relying on what has been returned by useState to update can yield incorrect results. Also the way to check for a change to count and update accordingly is by using useEffect with count as a dependency. The console.log() in your example will still log the old state as state updates are async and can only be seen during the next render.
const [count, setCount] = useState(0)
const [color, setColor] = useState('black')
function handleDecrement () {
setCount(current => current - 1);
}
function handleIncrement () {
setCount(current => current + 1)
}
useEffect(() => {
// Less than 0 make it red
if (count < 0) {
setColor('red')
console.log(count)
// Greater than 1 make it green
} else if (count > 0 ) {
setColor('green')
console.log(count)
// If it's 0 just keep it black
} else {
setColor('black')
console.log(count)
}
}, [count]);

useEffect enters infinite loop when using React in Hooks

I'm paging with react. When doing this, I transfer the state in a parent component here. I transfer the information I transfer to a different state with a new array, but when I do it with useEffect, it enters an infinite loop. I couldn't set a dene state.
const Pagination = ({ posts }) => {
const gecici = posts
const sabit = 5;
const [dene, setDene] = useState([])
const [degisken, setDegisken] = useState(0)
useEffect(() => {
degistir()
console.log(dene)
}, [dene])
const degistir =() => {
var i = 1,
j;
if (sabit <= gecici.length) {
for (j = 0; j < (gecici.length / sabit); j++) {
setDene([
{
id: i, include:
gecici.slice(degisken, degisken + sabit)
}
]);
i += 1;
setDegisken(degisken+sabit)
}
}
}
Since I'm paging, I'm adding content up to a 'sabit' value on each page. The value i indicates the page number here.
The value of j is to fill the array up to the total number of pages.
The way the useEffect dependency array works is by checking for strict (===) equivalency between all of the items in the array from the previous render and the new render. Therefore, putting an array into your useEffect dependency array is extremely hairy because array comparison with === checks equivalency by reference not by contents.
const foo = [1, 2, 3];
const bar = foo;
foo === bar; // true
const foo = [1, 2, 3];
const bar = [1, 2, 3];
foo === bar; // false
So, since you have this:
useEffect(() => {
degistir()
console.log(dene)
}, [dene])
Your component enters an infinite loop. degistir() updates the value of dene to a new array. But even if the contents are the same, the reference of dene has changed, and the useEffect callback fires again, ad infinitum.
The solution depends a bit on what exact behavior you want to have happen. If you want the useEffect callback to trigger every time the size of the array changes, then simply use the length of the array in the dependency array, instead of the array itself:
useEffect(() => {
degistir()
console.log(dene)
}, [dene.length])
A less ideal solution is to convert your array into a string and compare the old stringified array to the new stringified array:
useEffect(() => {
degistir()
console.log(dene)
}, [JSON.stringify(dene)])
This is extremely inefficient but might work if the number of items in the array is small.
The best option is probably to get rid of the useEffect entirely. It is unlikely that you actually need it. Instead, you can probably accomplish your pagination as a pure result of: 1) The total number of posts, and 2) the current page. Then just render the correct posts for the current page, and have a mechanism for updating the current page value.
Perhaps something like this? The naming of your variables is confusing to me since I speak English only:
const Pagination = ({ posts }) => {
const gecici = posts
const sabit = 5;
const [degisken, setDegisken] = useState(0)
let dene = [];
var i = 1,
j;
if (sabit <= gecici.length) {
for (j = 0; j < (gecici.length / sabit); j++) {
dene = [
{
id: i, include:
gecici.slice(degisken, degisken + sabit)
}
];
i += 1;
}
}

Resources