Use setState in a loop - reactjs

react hook useState works like setState when updating the state.
That's mean asynchronously operation.
My project lean on updating state with some timeout of 2 sec.
If I have an array with 3 values and I want to make a loop and change the state every 2 sec, but the operation just take the last state like an asynchronous functions.
any idea?
for (let i=0; i<arr.length; i++) {
setTimeout(() => setStr(arr[i]), 2000);
}

To give some idea, you need a setInterval that uses next item in array every time it is invoked, something like this:
const current = 0;
const interval = setInterval(() => {
if (current === arr.length - 1) {
clearInterval(interval);
}
setStr(arr[current])
current++;
}, 2000);
Of course you need to run it as an effect, keep the variables in a ref, clear interval on unmount etc.

So I found the answer myself.
create a promise by instantiate the promise class.
then make a function with set timout and resolve the setState function.
call it from from other function and loop in over in.

Related

when changing deps, useEffect re-runs the setInterval inside itself. how to avoid?

There is a web application - a simple widget that loads local time from the WorldTime API for different regions.
The task is to fetch time from the server every five seconds.
To do this, I used setInterval inside useEffect , in which I put the function where fetch occurs. However, there is a problem - when selecting / changing the region, I have to wait five seconds until setInterval completes.
setInterval, as I understand it, is restarted inside useEffect when deps changes, when a new region is selected.
How can this problem be solved?
App on github: https://mmoresun.github.io/live-clock/
The code itself: https://codesandbox.io/s/codepen-with-react-forked-66iz3n?file=/src/App.js
You can call directly the same getTime function before setting the interval
useEffect(() => {
getTime(value);
const myInterval = setInterval(() => {
// refreshing with setInterval every 5 seconds
getTime(value);
}, 5000);
return () => clearInterval(myInterval);
}, [value]); /
As for your codepen example, you are continuosly calling the timezone api, because you fetch it inside the SearchPanel component. That's wrong, remove it.

How setState affects setInterval?

I've coded simple timer, but when I try to console.log(time)(It is in handleStart), I get the same output 0, even if setTime() is called.
Here's the part of code, where I console.log(time) (It is in handleStart, you should click the Start button in order to see console.log):
const handleStart = () => {
setIsCounting(true);
timerIntervalRef.current = setInterval(() => {
console.log(time);
setTime((prevState) => {
localStorage.setItem("time", `${prevState + 1}`);
return prevState + 1;
});
}, 1000);
};
Link to Sandbox
Please, explain me, why it works that way, cause I think, that, the callback in setInterval has a reference to a time, which is defined above, so every time this callback gets called, it should climb through closure to the the point, where time is defined, so it should get renewed value.
time is a local const, which your interval function closes over. As a const, it can never change, so you're always logging out the original value. Even if you used let the behavior would be the same, because calling setTime does not change the value in the local variable. Rather it asks react to rerender your component. On that new render, a new local variable will be created with the new value, but code in the old render (including the code in the setInterval) still only has the old variable in its scope and cannot access the new one.
If you'd like to verify that the component is rerendering, you can move your log statement into the body of the component.
console.log('rendering with', time); // moved outside of handle start
const handleStart = () => {
// ... normal handleStart function, minus the console.log
}
Or if you want a log statement at the time you set the state, you could move it inside the set state function, since that gets passed the latest value of the state and doesn't depend on a closure variable:
setTime((prevState) => {
console.log(prevState);
localStorage.setItem("time", `${prevState + 1}`);
return prevState + 1;
});
inside of settime you are getting the renewed value (prevState), the "time" is referencing to the initial time, consoling will obviously refer to initial value, I think you should console.log(prevState)

useEffect dependency array understanding

Hello I am learning about the useEffect hook in react, and I need some clarification. It is my understand that when we provide an empty dependency array that the code inside of the useEffect hook will only run once during the application's initial mount. I need some helping understand why the code below runs every time I refresh the page even though I provided an empty dependency array?
Thank you
const [numberOfVistors, setnumberOfVistors] = useState(() => {
localStorage.getItem("numberOfVistorsKey")
});
useEffect (() => {
let vistorCount = localStorage.getItem("numberOfVistorsKey")
if (vistorCount > 0)
{
vistorCount = Number(vistorCount) + 1
localStorage.setItem("numberOfVistorsKey", vistorCount)
setnumberOfVistors(vistorCount)
}
else
{
vistorCount = 1
setnumberOfVistors(vistorCount)
localStorage.setItem("numberOfVistorsKey", vistorCount)
}
}, [])
The context of an application does not persist across multiple pageloads or refreshes except through the few ways that allow for persistent data - such as local storage, cookies, and an API with a server somewhere. For the same reason, doing
let num = 5;
button.onclick = () => { num = 10; }
results in num still being 5 after you click and reload the page - because reloading the page starts the script from the very beginning again, on the new page.
If you want to keep track of whether that particular section of code has ever run before, use a flag in storage, eg:
useEffect(() => {
if (localStorage.hasVisitedBefore) return;
localStorage.hasVisitedBefore = 'yes';
// rest of effect hook
, []);
I need some helping understand why the code below runs every time I refresh the page even though I provided an empty dependency array?
Every time you refresh the page, the React component mounts for the first time.
useEffect(..., []) was supplied with an empty array as the dependencies argument. When configured in such a way, the useEffect() executes the callback just once, after initial mounting.
Try going through it !!
Whenever you refresh the page, React component will be re-rendered. Therefore useEffect() is called everytime.
In order to avoid it, you have to do like this.
const [numberOfVistors, setnumberOfVistors] = useState(() => {
localStorage.getItem("numberOfVistorsKey")
});
useEffect (() => {
let vistorCount = localStorage.getItem("numberOfVistorsKey")
if (vistorCount > 0)
{
vistorCount = Number(vistorCount) + 1
localStorage.setItem("numberOfVistorsKey", vistorCount)
setnumberOfVistors(vistorCount)
}
else
{
vistorCount = 1
setnumberOfVistors(vistorCount)
localStorage.setItem("numberOfVistorsKey", vistorCount)
}
}, [numberOfVistors])
This code will render your component whenever numberOfVistors are changed.

Infinite loop in useEffect fix?

I am currently pulling data from a firebase realtime database and the data will initial populate. When I refresh the page though, the contents disappear. I added an empty array at the end of the UseEffect() to stop the infinite loop issue that we were having, but it seems to stop updating our array when refreshed.
useEffect(() => {
let jobs = [];
firebase.database().ref("1h5GOL1WIfNEOtcxJVFQ0x_bgJxsPN5zJgVJOePmgJOY/Jobs").on("value", snapshot => {
snapshot.forEach(snap => {
jobs.push(snap.val());
});
})
populateJobs(jobs);
},[]);
As ray commented, it does matter how populateJobs is defined. But at a first guess, you'll need to call that from inside the callback:
useEffect(() => {
firebase.database().ref("1h5GOL1WIfNEOtcxJVFQ0x_bgJxsPN5zJgVJOePmgJOY/Jobs").on("value", snapshot => {
let jobs = [];
snapshot.forEach(snap => {
jobs.push(snap.val());
});
populateJobs(jobs);
})
},[]);
I assume populateJobs is function declared in your scope.
If so, you may want to wrap it in useCallback to ensure the function reference doesn't change.
const populateJobsFixed= useCallback(populateJobs, [])
useEffect(() => {
...
populateJobsFixed(jobs);
},[populateJobsFixed]);
populateJobs is a dependency of the useEffect
You need to have the dependency list be
},[populateJobs]);
Instead of an empty array
The second argument of useEffect(()=>{},[]) which takes an array of argument tells react on what changes your callback passes there should run.
If passed an empty array it runs only once, behaving like componentdidmount method
When a variable is passed, it runs every time the value of the variable is changed.
Also if we pass an object as second parameter it will check for the reference change too.

Issues accessing react state in firestore onSnapshot listener

I want to wait to apply state updates from the back-end if a certain animation is currently running. This animation could run multiple times depending on the game scenario. I'm using react-native with hooks and firestore.
My plan was to make an array that would store objects of the incoming snapshot and the function which would use that data to update the state. When the animation ended it would set that the animation was running to false and remove the first item of the array. I'd also write a useEffect, which would remove the first item from the array if the length of the array had changed.
I was going to implement this function by checking whether this animation is running or whether there's an item in the array of future updates when the latest snapshot arrives. If that condition was true I'd add the snapshot and the update function to my array, otherwise I'd apply the state update immediately. I need to access that piece of state in all 3 of my firestore listeners.
However, in onSnapshot if I try to access my state it'll give me the initial state from when the function rendered. The one exception is I can access the state if I use the function to set the state, in this case setPlayerIsBetting and access the previous state through the function passed in as a callback to setPlayerIsBetting.
I can think of a few possible solutions, but all of them feel hacky besides the first one, which I'm having trouble implementing.
Would I get the future state updates if I modify the useEffect for the snapshots to not just run when the component is mounted? I briefly tried this, but it seems to be breaking the snapshots. Would anyone know how to implement this?
access the state through calling setPlayerIsBetting in all 3 listeners and just set setPlayerIsBetting to the previous state 99% of the time when its not supposed to be updated. Would it even re-render if nothing is actually changed? Could this cause any other problems?
Throughout the component lifecycle add snapshots and the update functions to the queue instead of just when the animation is running. This might not be optimal for performance right? I wouldn't have needed to worry about it for my initial plan to make a few state updates after an animation runs since i needed to take time to wait for the animation anyway.
I could add the state I need everywhere on the back-end so it would come in with the snapshot.
Some sort of method that removes and then adds the listeners. This feels like a bad idea.
Could redux or some sort of state management tool solve this problem? It would be a lot of work to implement it for this one issue, but maybe my apps at the point where it'd be useful anyway?
Here's my relevant code:
const Game = ({ route }) => {
const [playerIsBetting, setPlayerIsBetting] = useState({
isBetting: false,
display: false,
step: Infinity,
minimumValue: -1000000,
maximumValue: -5000,
});
const [updatesAfterAnimations, setUpdatesAfterAnimations] = useState([]);
// updatesAfterAnimations is currently always empty because I can't access the updated playerIsBetting state easily
const chipsAnimationRunningOrItemsInQueue = (snapshot, updateFunction) => {
console.log(
"in chipsAnimationRunningOrItemsInQueue playerIsBetting is: ",
playerIsBetting
); // always logs the initial state since its called from the snapshots.
// So it doesn't know when runChipsAnimation is added to the state and becomes true.
// So playerIsBetting.runChipsAnimation is undefined
const addToQueue =
playerIsBetting.runChipsAnimation || updatesAfterAnimations.length;
if (addToQueue) {
setUpdatesAfterAnimations((prevState) => {
const nextState = cloneDeep(prevState);
nextState.push({ snapshot, updateFunction });
return nextState;
});
console.log("chipsAnimationRunningOrItemsInQueue returns true!");
return true;
}
console.log("chipsAnimationRunningOrItemsInQueue returns false!");
return false;
};
// listener 1
useEffect(() => {
const db = firebase.firestore();
const tableId = route.params.tableId;
const unsubscribeFromPlayerCards = db
.collection("tables")
.doc(tableId)
.collection("players")
.doc(player.uniqueId)
.collection("playerCards")
.doc(player.uniqueId)
.onSnapshot(
function (cardsSnapshot) {
if (!chipsAnimationRunningOrItemsInQueue(cardsSnapshot, updatePlayerCards)) {
updatePlayerCards(cardsSnapshot);
}
},
function (err) {
// console.log('error is: ', err);
}
);
return unsubscribeFromPlayerCards;
}, []);
};
// listener 2
useEffect(() => {
const tableId = route.params.tableId;
const db = firebase.firestore();
const unsubscribeFromPlayers = db
.collection("tables")
.doc(tableId)
.collection("players")
.onSnapshot(
function (playersSnapshot) {
console.log("in playerSnapshot playerIsBetting is: ", playerIsBetting); // also logs the initial state
console.log("in playerSnapshot playerIsBetting.runChipsAnimation is: "playerIsBetting.runChipsAnimation); // logs undefined
if (!chipsAnimationRunningOrItemsInQueue(playersSnapshot, updatePlayers)) {
updatePlayers(playersSnapshot);
}
},
(err) => {
console.log("error is: ", err);
}
);
return unsubscribeFromPlayers;
}, []);
// listener 3
useEffect(() => {
const db = firebase.firestore();
const tableId = route.params.tableId;
// console.log('tableId is: ', tableId);
const unsubscribeFromTable = db
.collection("tables")
.doc(tableId)
.onSnapshot(
(tableSnapshot) => {
if (!chipsAnimationRunningOrItemsInQueue(tableSnapshot, updateTable)) {
updateTable(tableSnapshot);
}
},
(err) => {
throw new err();
}
);
return unsubscribeFromTable;
}, []);
I ended up not going with any of the solutions I proposed.
I realized that I could access the up to date state by using a ref. How to do it is explained here: (https://medium.com/geographit/accessing-react-state-in-event-listeners-with-usestate-and-useref-hooks-8cceee73c559) And this is the relevant code sample from that post: (https://codesandbox.io/s/event-handler-use-ref-4hvxt?from-embed)
Solution #1 could've worked, but it would be difficult because I'd have to work around the cleanup function running when the animation state changes. (Why is the cleanup function from `useEffect` called on every render?)
I could work around this by having the cleanup function not call the function to unsubscribe from the listener and store the unsubscribe functions in state and put them all in a useEffect after the component mounts with a 2nd parameter that confirmed all 3 unsubscribe functions had been added to state.
But if a user went offline before those functions were in state I think there could be memory leaks.
I would go with solution #1: In the UseEffect() hooks you could put a boolean flag in so the snapshot listener is only set once per hook. Then put the animation state property in the useEffect dependency array so that each useEffect hook is triggered when the animation state changes and you can then run whatever logic you want from that condition.

Resources