React js call setInterval with conditon and clear setInterval - reactjs

how can i call two permanent services, i want to call the first service every 5 seconds, after once results of the first service return a value different from 0, I want to call a second service with the value returned by the first service every 5 seconds also, until I receive a value, How can I do this with react setInterval doesn't work.
const {info,data} = useSelector((state) => ({
info : state.reducerData.info,
data : state.reducerData.data,
})
useEffect(() =>{
var refreshIntervalId = setInterval(() => {
dispatch(getData(id));
},(5000))
if(data?.sum != 0){
clearInterval(refreshIntervalId)
}
},[id])
useEffect(() =>{
var interval = setInterval(() => {
if(data?.sum !=0){
dispatch(getInfoData())
}
},(5000))
if( info == sum){
clearInterval(interval)
}
},[data])

Related

How to use setInterval with a pause between loops?

In my react component, once the component loads, I am trying to repeat a task every 100ms and after 3 repetitions pause for 1 second. Then repeat this pattern indefinitely.
I want to achieve this output:
1 // pause 100ms
2 // pause 100ms
3 // pause 100ms
// pause 1second
... repeat
I tried something like this
useEffect(() => {
let i = 0
function increment() {
if (i === 3) {
// i = 0
// restart timer?
// return?
}
i++
console.log(i)
}
const incrementTimer = setInterval(increment, 100)
setInterval(() => {
clearInterval(incrementTimer)
}, 1000)
}, [])
You can use a recursive function within useEffect hook:
useEffect(() => {
function start(i = 1) {
const reset = i > 3;
const time = reset ? 1000 : 100;
const timeout = setTimeout(() => {
task(i, time); // -> run your task
start(reset ? 1 : i + 1); recursive call to schedule next task
clearTimeout(timeout); // -> clear
}, time);
}
start();
}, []);
function task(i, time) {
console.log('task is running: ', i, time);
}
Working example: https://stackblitz.com/edit/react-jenzmw?file=src%2FApp.js
You can achieve this via setTimeout.
useEffect(() => {
let i = 0
let incrementTimer
function increment() {
if (i === 3) {
// i = 0
// restart timer?
// return?
}
i++
console.log(i)
incrementTimer = setTimeout(increment, 100)
}
increment()
setInterval(() => {
clearInterval(incrementTimer)
}, 1000)
}, [])
First you need to define a state to determine at which level the component is.
const [levelIndex, setLevelIndex] = useState(0);
Then create an array for execution time at each level (This is For the case that you are looking for, it can be changed depending on what time spans are needed or which pattern a user wants).
const intevalExecutionTime = [100,100,100,1000];
Next create a useEffect hook like this:
useEffect(() => {
const timer = setInterval(() => {
//**write the task that you want to be done.**
if(levelIndex === 3){
setLevelIndex(0);
console.log(`one second paused`);
}
else{
setLevelIndex(levelIndex+1);
console.log('task is done.');
}
clearInterval(timer);
}, intevalExecutionTime[levelIndex])
}, [levelIndex]);
This hook callback will set an interval based on the interval execution time level. Every time the setInterval callback is triggered the older interval object will be removed and it changes the levelIndex and the useEffect callback will be called once again.

React state's previous value equals new value with setInterval

I am setting up a setInterval with some VideoPlayer iFrame. Every 10 seconds or so, I need to call an API to update back-end with new state of the video played.
I want to have another check on client, if the data from previous state is same as new one (This case occurs when the video is paused for longer duration) I don't want to call the back-end API.
const [coveredArray, setCoveredArray] = useState([]);
const intervalPeriod = 5000;
const interval = useRef();
useEffect(() => {
if (typeof window !== "undefined" && isOpen && player) {
console.log('SUB');
// Setup an interval to update watch time
interval.current = window.setInterval(async () => {
const totalCoveredArray = await player?.api.getTotalCoveredArray();
setCoveredArray((prevCoveredArray) => {
console.log('1', prevCoveredArray);
console.log('2', totalCoveredArray);
if (!isEqual(prevCoveredArray, totalCoveredArray)) {
console.log('New');
return totalCoveredArray;
} else {
console.log('Same');
return prevCoveredArray;
}
});
}, intervalPeriod);
return () => {
// Clear interval on exit
console.log('UN-SUB')
setCoveredArray([]);
return window.clearInterval(interval.current)
};
}
}, [isOpen, player]);
The problem with this code is, prevCoveredArray and totalCoveredArray are same everytime the interval is run. How is previousState similar to newState (which is not set yet)?
This is logged in console:
Any help/pointer would be helpful, thanks.

Use effect doesn't see state

i'm working on a continuous notification (every 3 seconds and max 1 min), until user accept pending orders.
This is my code.
usePrevious is custom hook that gives me previous value of state.
I don't mind why, when setTimeout executes clearInterval(), 'notification' is null.
This problem cause loop in sound.
[.....]
const [notification, setNotification] = useState(null)
const previousOrderCount = usePrevious(newOrderCount)
const previousPendingReservationsLength = usePrevious(
pendingReservationsLength
)
const isToneActive = useRef(false)
// sound notify for new orders
useEffect(() => {
if (
!isToneActive.current &&
newOrderCount > previousOrderCount &&
user?.profile?.role === "manager"
) {
// this prevent multiple triggers of sound
isToneActive.current = true
setNotification(setInterval(() => playNotification(), 3000))
setTimeout(() => {
clearInterval(notification)
isToneActive.current = false
}, 60000)
}
}, [
newOrderCount,
playNotification,
user,
previousOrderCount,
stopNotify,
notification,
setNotification,
])
[....]
I would use another React ref to hold a reference to the interval timer. The issue here is that React state updates are asynchronous, so the setNotification state update doesn't immediately update the notification state for the setTimeout callback that encloses the current null state value.
const notificationRef = React.useRef(null);
...
notificationRef.current = setInterval(() => playNotification(), 3000);
setTimeout(() => {
clearInterval(notificationRef.current);
notificationRef.current = null;
isToneActive.current = false;
}, 60000);

Update non state variable in React useEffect

I have an application the receives new data over a WebSocket every second. Each second I receive 10 to 15 messages that I need to store in and display. I am currently updating a state array each time I receive new data but the effect is that I re-render the screen 10 to 15 times per second.
What I want to achieve is to store the incoming data in an array but only update the screen once every second.
My approach that I can't get working is to create a non-state array that is updated when new data is received and copy that data to a state array every second with a timer.
This is the declaration of the state array:
const [boatData2, _setBoatData2] = useState({});
const boatDataRef = useRef(boatData2);
const setBoatData2 = (update) => {
boatDataRef.current = update;
_setBoatData2(update);
}
This is the hook code where the data is received:
useEffect(() => {
if (!ws.current) return;
ws.current.onmessage = e => {
setDataFlowing(true);
setDataAge(0);
setScreenUpdates(screenUpdates => screenUpdates + 1);
//console.log('New Data');
const message = JSON.parse(e.data);
if (message.updates && message.updates.values) {
message.updates[0].values.forEach(obj => {
let newPath = obj.path.split('.').join("");
const update = {
path: obj.path,
value: obj.value,
timestamp: message.updates[0].timestamp,
valid: true,
age: 0,
};
now = Date.parse(message.updates[0].timestamp);
setBoatData2({ ...boatDataRef.current, [newPath]: update });
});
}
};
}, []);
This is the code that runs every second:
useEffect(() => {
let interval = null;
if (isActive) {
interval = setInterval(() => {
setSeconds(seconds => seconds + 1);
let boatdata = boatData2;
//console.log(boatData3);
Object.values(boatdata).forEach(val => {
val.age = val.age + 1;
if (val.age > 30) {
val.valid = false;
}
});
setBoatData2(boatdata);
setDataAge(dataAge => dataAge + 1);
if (dataAge > 60) {
setDataFlowing(false);
}
}, 1000);
} else if (!isActive && seconds !== 0) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [isActive, seconds, boatData2]);
You can do this with the help of useRef .
const messageRef = useRef([]);
This creates a object inside messageRef called current which we can mutate and mutating it will not trigger a re-render. Now your messageRef will be something like this
{
current: []
}
Now whenever you get the message from the websocket push the message into this ref as
messageRef.current.push(your message)
Now inside your function which updates the state after some xyz second . You can use this Ref to update the state
setYourMessages(messageRef.current);
messageRef.current = []; // do this after you state update call. Else you will be pushing duplicate messages into the state

Can't run functions in .then() Axios React

I have a webpage where I fetch the data with async axios and then make calculations with them.
Here is the code snippet:
const FetchData = async () =>{
console.log("FETCH CALLED");
await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
.then(resp => {
setStockData(resp.data);
calculateTrend();
calculateTrendDirection();
})
}
Here, I get the error at calculateTrend() function. My question is, that this .then() should run when the response has arrived, but it seems that it runs before. Because both calculateTrend and calculateTrendDirection works with this fetched data
Edit: The error I am getting is Cannot read property 'previousClosePrice' of undefined. I am sure this exist in the object so mispelling is not a problem
Edit2: I edited my Component according to your solutions and one happens to work, the only thing is that the fetching gets to an infinite loop and fetches multiple times a second. My suspect is the dependencies in useEffect, but I am not sure what to set there.
Here is my full component:
function StockCard(props) {
const [FetchInterval, setFetchInterval] = useState(300000);
const [StockData, setStockData] = useState({});
const [TrendDirection, setTrendDirection] = useState(0);
const [Trend, setTrend] = useState(0);
const FetchData = async () =>{
console.log("FETCH CALLED");
const resp = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`)
setStockData(resp.data);
}
const calculateTrendDirection = () => {
console.log(StockData.lastPrice);
if(StockData.lastPrice.currentPrice > StockData.lastPrice.previousClosePrice){
setTrendDirection(1);
} else if (StockData.lastPrice.currentPrice < StockData.lastPrice.previousClosePrice){
setTrendDirection(-1);
} else {
setTrendDirection(0);
}
}
const calculateTrend = () => {
console.log(StockData.lastPrice);
var result = 100 * Math.abs( ( StockData.lastPrice.previousClosePrice - StockData.lastPrice.currentPrice ) / ( (StockData.lastPrice.previousClosePrice + StockData.lastPrice.currentPrice)/2 ) );
setTrend(result.toFixed(2));
}
useEffect(() => {
FetchData();
if(StockData.lastPrice){
console.log("LÉTEZIK A LAST PRICE")
calculateTrend();
calculateTrendDirection();
}
const interval = setInterval(() => {
FetchData();
}, FetchInterval)
return() => clearInterval(interval);
},[StockData, FetchData, FetchInterval, calculateTrend, calculateTrendDirection]);
return(
<div>
<CryptoCard
currencyName={StockData.lastPrice? StockData.name : "Name"}
currencyPrice={StockData.lastPrice? `$ ${StockData.lastPrice.currentPrice}` : 0}
icon={<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/4/46/Bitcoin.svg/2000px-Bitcoin.svg.png"/>}
currencyShortName={StockData.lastPrice? StockData.symbol : "Symbol"}
trend={StockData.lastPrice? `${Trend} %` : 0}
trendDirection={StockData.lastPrice? TrendDirection : 0}
chartData={[9200, 5720, 8100, 6734, 7054, 7832, 6421, 7383, 8697, 8850]}
/>
</div>
)
The then block is called only after the promise is fulfilled, so the data is available at that point.
From what I can see, the problem is setStockData tries to set the stockData state variable with the response, but calculateTrend and calculateTrendDirection are called before the state is set because updating state values is batched.
There are several solutions to the problem.
Solution 1:
You can call the two functions after the state is set:
setStockData(resp.data, () => {
calculateTrend();
calculateTrendDirection();
});
Solution 2:
You can use useEffect to call the functions again after the state is updated:
useEffect(() => {
if (stockData) { // or whatever validation needed
calculateTrend();
calculateTrendDirection();
}
}, [stockData]);
Solution 3:
You can pass the parameters to the method:
calculateTrend(resp.data);
calculateTrendDirection(resp.data);
The best option? I think #2, because it also makes sure that the trend and trend direction are re-calculated whenever stock data is updated (from whatever other causes).
I guess in calculateTrend you are using the data which setStockData sets to the state, if that is the case
setState is not happening right after you call the setState, if you want something to execute after correctly update the State then should look at something like this
setStockData(resp.data, () => {
calculateTrend();// this will call once the state gets changed
});
or you could use useEffect
useEffect(() => {
calculateTrend(); // this will call every time when stockData gets changed
}, [stockData])
If you are using stockData inside calculateTrend function and setStockData is an async function, move calculateTrend function to useEffect using stockData as dependency, so every time stockData is updated, calculateTrend and calculateTrendDirection will be called:
useEffect(() => {
const interval = setInterval(() => {
FetchData();
}, FetchInterval);
return() => clearInterval(interval);
}, [FetchInterval]);
useEffect(() => {
if(StockData.lastPrice){
console.log("LÉTEZIK A LAST PRICE")
calculateTrend();
calculateTrendDirection();
}
}, [StockData]);
const FetchData = async () =>{
console.log("FETCH CALLED");
const res = await Axios.get(`http://localhost:8080/stock/getquote/${props.API}`);
setStockData(resp.data);
}

Resources