React state taking previous initialized values when updating - reactjs

I am having an issue with react.
I have an array of states of a task whether it is pending or completed or running.
I have an used an array in use state like this
const [messageState, setMessageState] = useState<string[]>(new Array(6).fill('Pending...'));
All that has been initialized to pending first.
And then the messages come from Kafka and according to that, I am updating the state.
useEffect(() => {
if(message.info && message.info.log.indexOf('fromScript') ! == -1) {
const {info} = message
const {err} = info
for(let i=0; i < 6; i++){
const array = setTableContent(i, err)
console.log(array);
setMessageState(array)
}
}
}
)
The function for setTableContent is
const setTableContent = (row: number, err: any) => {
const value = getStatus(row, err);
const newArray = [...messageState]
newArray[row] = value;
return newArray;
}
where getStatus is a function which fetches the status for all the tasks on every kafka message.
Now the problem is that every time even if I am updating the state of messageType in useEffect snippet, I am still getting the same initialized values on the 3rd line of setTableContent function. Why is this happening I am not able to understand.
Please help. Thank you in advance.

I am still not sure why I can't update an array state in a loop but I have found a turn around for this.
Rather than putting
setMessageState() in useState, i have put the for loop in the function setTableContent() itself.
const setTableContent = (row: number, err: any) => {
const values = []
for(let i=0; i < 6; i++) {
values.push(getStatus(i, err));
}
setMessageType(values);
}
And now its working

Related

React State Fails To Update with UseEffect

When attempting to update an array via React state management, the state array is populated, but the user interface fails to update. The user interface only updates after I click on the navbar, and reroute to the current page (in which case useEffect does not run again, but the UI is updated).
State Code
const[isFetched, setIsFetched] = useState(false);
const[balances, setBalances] = useState<IBalance[]>([]);
const[num, setNum] = useState(0);
useEffect(() => {
console.log(balances);
// LOGS A POPULATED ARRAY
console.log(balances.length);
// LOGS 0
}, [balances]);
useEffect(() => {
const fetchBalances = async() =>{
let bals:IBalance[] = await kryptikService.getBalanceAllNetworks(kryptikWallet);
console.log("RECIEVED BALANCES:");
console.log(bals);
console.log(bals.length);
setBalances(bals);
setIsFetched(true);
}
fetchBalances();
}, []);
UI Code
<h2>Your Balances</h2>
<Divider/>
{
!isFetched?<p>Loading Balances.</p>:
<ul role="list" className="divide-y divide-gray-200 dark:divide-gray-700">
{balances.map((balance:IBalance) => (
<ListItem title={balance.fullName} imgSrc={balance.iconPath} subtitle={balance.ticker} amount={balance.amountCrypto}/>
))}
</ul>
}
</div>
Fetch Handler (called in UseEffect)
getBalanceAllNetworks = async(walletUser:IWallet):Promise<IBalance[]> =>{
let networksFromDb = this.getSupportedNetworkDbs();
// initialize return array
let balances:IBalance[] = [];
networksFromDb.forEach(async nw => {
let network:Network = new Network(nw.fullName, nw.ticker);
let kryptikProvider:KryptikProvider = await this.getKryptikProviderForNetworkDb(nw);
if(network.getNetworkfamily()==NetworkFamily.EVM){
if(!kryptikProvider.ethProvider) throw Error(`No ethereum provider set up for ${network.fullName}.`);
let ethNetworkProvider:JsonRpcProvider = kryptikProvider.ethProvider;
console.log("Processing Network:")
console.log(nw.fullName);
// gets all addresses for network
let allAddys:string[] = await walletUser.seedLoop.getAddresses(network);
// gets first address for network
let firstAddy:string = allAddys[0];
console.log(`${nw.fullName} Addy:`);
console.log(firstAddy);
console.log(`Getting balance for ${nw.fullName}...`);
// get provider for network
let networkBalance = await ethNetworkProvider.getBalance(firstAddy);
console.log(`${nw.fullName} Balance:`);
console.log(networkBalance);
// prettify ether balance
let networkBalanceAdjusted:Number = BigNumber.from(networkBalance)
.div(BigNumber.from("10000000000000000"))
.toNumber() / 100;
let networkBalanceString = networkBalanceAdjusted.toString();
let newBalanceObj:IBalance = {fullName:nw.fullName, ticker:nw.ticker, iconPath:nw.iconPath,
amountCrypto:networkBalanceString}
// add adjusted balance to balances return object
balances.push(newBalanceObj);
}
});
return balances;
}
Note: The array is a different reference, so there should be no issue with shallow equality checks. Also, the updated balances array contains objects, but the length is logged as zero as shown in the first code snippet. Any help will be much apreciated!
Issue
The issue is that you are iterating the networksFromDb array in a forEach loop with an asynchronous callback. The asynchronous callback ins't the issue, it is that Array.protptype.forEach is synchronous, the the getBalanceAllNetworks callback can't wait for the loop callbacks to resolve. It returns the empty balances array to the caller before the array is populate.
The array is still populated however, and the clicking the link is enough to trigger a React rerender and expose the mutated balances state array.
Solution
Instead of using a .forEach loop for the asynchronous callback, map networksFromDb to an array of Promises and use Promise.all and wait for them all to resolve before returning the populated balances array.
Example:
const getBalanceAllNetworks = async (
walletUser: IWallet
): Promise<IBalance[]> => {
const networksFromDb = this.getSupportedNetworkDbs();
const asyncCallbacks = networksFromDb
.filter((nw) => {
const network: Network = new Network(nw.fullName, nw.ticker);
return network.getNetworkfamily() == NetworkFamily.EVM;
})
.map(async (nw) => {
const kryptikProvider: KryptikProvider = await this.getKryptikProviderForNetworkDb(
nw
);
if (!kryptikProvider.ethProvider) {
throw Error(`No ethereum provider set up for ${network.fullName}.`);
}
const ethNetworkProvider: JsonRpcProvider = kryptikProvider.ethProvider;
// gets all addresses for network
const allAddys: string[] = await walletUser.seedLoop.getAddresses(
network
);
// gets first address for network
const firstAddy: string = allAddys[0];
// get provider for network
const networkBalance = await ethNetworkProvider.getBalance(firstAddy);
// prettify ether balance
const networkBalanceAdjusted: Number =
BigNumber.from(networkBalance)
.div(BigNumber.from("10000000000000000"))
.toNumber() / 100;
const networkBalanceString = networkBalanceAdjusted.toString();
const newBalanceObj: IBalance = {
fullName: nw.fullName,
ticker: nw.ticker,
iconPath: nw.iconPath,
amountCrypto: networkBalanceString
};
// add adjusted balance to balances return object
return newBalanceObj;
});
const balances: IBalance[] = await Promise.all(asyncCallbacks);
return balances;
};
You are mutating balances instead of updating it.
Change balances.push to setBalances(prevState => [...prevState, newBalance])

Rendering html in react with hooks not working

I have a Task component which shows what task i need to do and when, and i want this to auto update when its time for it to change. So i was trying to do something like this
function Task(){
const [data, setData] = useState();
const [currentTask, setTask] = useState();
React.useEffect(() => {
fetch("/api")
.then((res) => res.json())
.then((data) => setData(data.tasks));
}, []);
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
return(
<div>
{console.log("inside return")}
<p>{currentTask? currentTask: "Loading..."}</p>
</div>
)
}
when I log currentTask I get back the result I want. However, before that, there are a few logs of undefined, but at the end, it prints out the actual value of the task and then logs "inside return". But the only thing getting rendered on my app is "loading...", why is this? currentTask has a value, am I missing something? I've tried all that I could but I couldn't understand it.
Thank you for your help!
This is happening because the function determineTask() is being called before the data is fetched. Data fetching often takes a couple seconds, and the determineTask() function is being called as soon as the component renders.
I would suggest putting the block of code:
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
inside a useEffect. useEffect has a second parameter of a dependency array that will trigger the useEffect to run again when one of the dependencies change.
In your first useEffect, you're changing the data state variable and then you want the currentTask state variable to change once you've retrieved the data.
So, your second useEffect will look something like:
useEffect(() => {
function determineTask(){
Object.keys(data).map(function(key, index){
if(msSinceDayBegan > data[key].startMs && msSinceDayBegan < data[key].endMs){
setTask(data[key].task);
const nextIndex = Object.keys(data)[index+1];
setTimeout(determineTask, (data[nextIndex].startMs - msSinceDayBegan))
}
})
}
if(data && !initialized){
initialized = true;
determineTask();
}
console.log(currentTask);
},[data]);

State value not updating in react

I'm working on my first React application and I'm not understanding why the State doesn't have the updated value.
Here is my code:
const SlideOutPanel = forwardRef((props: any, ref: any) => {
const initCss: string = 'is-slide-out-panel';
const [compClass, setCompClass] = useState(initCss);
useImperativeHandle(ref, () => ({
open() {
open();
},
close() {
close();
},
}));
function refresh(): any {
let classVal: string = compClass;
if (props.direction === 'left-to-right') {
classVal = `${classVal} left-to-right`;
} else if (props.direction === 'right-to-left') {
classVal = `${classVal} right-to-left`;
}
if (Types().boolVal(props.userOverlay)) {
classVal = `${classVal} use-overlay`;
}
if (Types().boolVal(props.pushMain)) {
classVal = `${classVal} push-effect`;
}
if (props.theme === 'dark') {
classVal = `${classVal} theme-dark`;
}
setCompClass(classVal);
let classValdd: string = compClass;
}
function open(): void {
let classVal: string = compClass;
}
useEffect(() => {
refresh();
}, []);
return (
<section id={id} className={compClass}>
<div className="content">{props.children}</div>
</section>
);
});
I call refresh() when the components first load, which basically sets the className based on the passed props. At the end of the function, I set state "setCompClass" the value of "classVal" which works as I verified in Chrome Debugger. But on the same function I have the following line "let classValdd: string = compClass;" just to check what the value of "compClass" is and its always "is-slide-out-panel".
At first I thought it has to do with a delay. So when I call open() to do the same check, the value is still "is-slide-out-panel". So I'm a bit confused. Am I not able to read the state value "compClass"? Or am I misunderstanding its usage?
Setting the state in React acts like an async function.
Meaning that when you set it, it most likely won't finish updating until the next line of code runs.
So doing this will not work -
setCompClass(classVal);
let classValdd: string = compClass;
You will likely still end up with the previous value of the state.
I'm not exactly sure what specifically you're trying to do here with the classValdd variable at the end of the function block, but with function components in React, if we want to act upon a change in a state piece, we can use the built-in useEffect hook.
It should look like this -
useEffect(() => {
// Stuff we want to do whenever compClass gets updated.
}, [compClass]);
As you can see, useEffect receives 2 parameters.
The first is a callback function, the second is a dependency array.
The callback function will run whenever there is a change in the value of any of the members in that array.

setState not maintaining set value after value set with Callback

I'm working on an application where I multiple some values and use the sum on a computation a bit further. I quickly realized that this.setState wasnt working and I saw a callback being said to use a callback so I used this.setState({ estAlumTxt: estLBCarbonAl }, () => { however something strange is still happening. If I display the estAlumTxt variable in the console it's correct but if I display it lower down, it no longer has it's value set. This is my code.
calWasteCarbon = (event) => {
if (`${this.state.reAlumDrop}` === '1') {
const estLBCarbonAl = (1 * -89.38);
//this.setState({ estAlumTxt: estLBCarbonAl }
this.setState({ estAlumTxt: estLBCarbonAl }, () => {
console.log(this.state.estAlumTxt) // Returns the correct value set
}) } else {
this.setState({ estAlumTxt: 0 })
}
if (`${this.state.recPlasDrop}` === '1') {
const estLBCarbonPlas = (1 * -35.56);
this.setState({ estPlasTxt: estLBCarbonPlas })
} else {
this.setState({ estPlasTxt: 0 })
}
alert(this.state.estAlumTxt); //Alerted value is wrong.
Even if console.log reads out estAlumTxt correctly, the alert has it wrong. Gr
Calls to this.setState are asynchronous, meaning that they do not occur synchronously at the time they are called, but may occur at a later time.
This means, that while your function is executing, state.estAlumTxt hasn't been updated yet when you call the alert function.
Let's refactor your function a bit so that we only need to call setState once.
First lets extract those number consts out of the function, and I would advise putting them outside the component. There is no need to recalculate those consts every time the function is called.
Then lets create a stateChange variable where we can add the properties that we want to conditionally change. After we check the conditions, lets call setState, and alert the value of the stateChange.estAlumTxt
const estLBCarbonAl = 1 * -89.38;
const estLBCarbonPlas = 1 * -35.56;
calWasteCarbon = (event) => {
const stateChange = {};
if (`${this.state.reAlumDrop}` === '1') {
stateChange.estAlumTxt = estLBCarbonAl;
} else {
stateChange.estAlumTxt = 0;
}
if (`${this.state.recPlasDrop}` === '1') {
stateChange.estPlasTxt = estLBCarbonPlas;
} else {
stateChange.estPlasTxt = 0;
}
this.setState(stateChange);
alert(stateChange.estAlumTxt);
};

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