React Lifecycles managing intervals with changing values - reactjs

In one of my components I have a useEffect setup where an interval is set to fetch a function.
It's basically set up as follows:
...
const token = useToken() // custom hook that updates whenever access_token updates
const fetchData = useCallback(() => {
callAPI(token)
}, [token])
useEffect(() => {
if (!token) return
fetchData()
const interval = setInterval(fetchData, 60000)
return () => {
clearInterval(interval)
}
}, [token]}
...
It is supposed fetchData every 60 seconds which it does.
What it also needs to do (which it doesn't) is whenever the token is updated, it should account for that.
What I've currently done to try solve that is clear the interval when the token changes and start the process over. But I think I've handled that incorrectly in my attempts above.
Any idea on how to accomplish this correctly?

the only thing missing is fetchData should be added to the dependency array to make sure that the useEffect uses the updated callback
useEffect(() => {
if (!token) return
fetchData()
const interval = setInterval(fetchData, 60000)
return () => {
clearInterval(interval)
}
}, [token, fetchData])
but you can also move the fetchData(this time fetchData doesn't have to be memorized with useCallback) function inside the useEffect, that way you can only have token as a dependency:
useEffect(() => {
const fetchData = () => {
if(!token) return;
callAPI(token)
};
fetchData();
const interval = setInterval(fetchData, 60000)
return () => {
clearInterval(interval)
}
}, [token])
Edit:
You can remove token form the useEffect this way:
const fetchData = useCallback(() => {
if(!token) return; // moved the token check here
callAPI(token)
}, [token])
useEffect(() => {
fetchData();
const interval = setInterval(fetchData, 60000)
return () => {
clearInterval(interval)
}
}, [fetchData])

Related

How to call an API every 5 minutes using React?

I've been trying to call the API every 5 min but the limit for setInterval doesn't allow that.
useEffect(() => {
setInterval(() => {
(() => {
const API_KEY = "C5EQJXXXXXXXXXXXX";
const name = "FB";
axios
.get(
`https://www.alphavantage.co/query?function=TIME_SERIES_INTRADAY&symbol=${name}&interval=5min&apikey=${API_KEY}`
)
.then(({ data }) => {
console.log(data);
console.log(data["Time Series (5min)"]);
for (let key in data["Time Series (5min)"]) {
setStocksX((prev) => [...prev, key]);
setStocksY((prev) => [
...prev,
data["Time Series (5min)"][key]["1. open"]
]);
}
//console.log(stocksX, stocksY);
});
})();
}, 30000);
});
Any advice would be appreciated!
Thanks
you can add setInterval with 60 * 5 * 1000 of time, in useEffect hook in first renderization. After you need clear setInterval when unmount component.
const ref = useRef(null)
useEffect(() => {
ref.current = setInterval(yourFunction, 5 * 60 * 1000);
return () => {
if(ref.current){
clearInterval(ref.current)
}
}
}, [])
I wrote this utility hook that I use in my React apps all the time:
import React from 'react';
type IntervalCallback = () => void;
function useDispatch(callback: IntervalCallback, delay: number): void {
const cachedCallback = React.useRef<IntervalCallback>();
React.useEffect(() => {
cachedCallback.current = callback;
}, [callback]);
React.useEffect(() => {
if (delay !== 0) {
const id = setInterval(() => cachedCallback?.current?.(), delay);
return () => clearInterval(id);
}
}, [delay]);
}
export const IntervalHooks = {
useDispatch,
};
I can then use it as such:
IntervalHooks.useDispatch(() => { console.log("hello"); }, 300000);
This gives me a lot of flexibility since I can dynamically update my interval if needed, and takes care of clean up when my component gets unmounted.
I presume that your hook is declaring a new setInterval every 0.5 seconds, possibly making your React App stop in order to avoid memory leak, you just need to declare one setInterval, this means that the second parameter of useEffect should have empty squared brackets
useEffect(() => {
setInterval(yourFunction, 300000); // The function will be called
},[]); // every 5 minutes

Fetch and setInterval react hooks problem

I recently used hooks with React to fetch data from server but i'm facing a problem with hooks. The code seems correct but it look like the useEffect isn't called at first time but 3 seconds after with the setInterval. I have blank table for 3 seconds before it appear. I want to directly show the data and call it 3 seconds later.
What is the correct way to use it ?
const [datas, setDatas] = useState([] as any);
useEffect(() => {
const id = setInterval(() => {
const fetchData = async () => {
try {
const res = await fetch(URL);
const json = await res.json();
setDatas(jsonData(json));
} catch (error) {
console.log(error);
}
};
fetchData();
}, TIME)
return () => clearInterval(id);
}, [])
You need to invoke fetchData once initially outside the interval. Define fetchData outside the interval.
useEffect(() => {
// (1) define within effect callback scope
const fetchData = async () => {
try {
const res = await fetch(URL);
const json = await res.json();
setDatas(jsonData(json));
} catch (error) {
console.log(error);
}
};
const id = setInterval(() => {
fetchData(); // <-- (3) invoke in interval callback
}, TIME);
fetchData(); // <-- (2) invoke on mount
return () => clearInterval(id);
}, [])
With React Hooks:
const [seconds, setSeconds] = useState(0)
const interval = useRef(null)
useEffect(() => { if (seconds === 60) stopCounter() }, [seconds])
const startCounter = () => interval.current = setInterval(() => {
setSeconds(prevState => prevState + 1)
}, 1000)
const stopCounter = () => clearInterval(interval.current)

Infinite loop in useEffect when setting state

I've got a question about useEffect and useState inside of it.
I am building a component:
const [id, setId] = useState(0);
const [currencies, setCurrencies] = useState([]);
...
useEffect(()=> {
const getCurrentCurrency = async () => {
const response = await fetch(`https://api.exchangeratesapi.io/latest?base=GBP`);
const data = await response.json();
const currencyArray = [];
const {EUR:euro ,CHF:franc, USD: dolar} = data.rates;
currencyArray.push(euro, dolar/franc,1/dolar);
console.log("currencyArray", currencyArray);
setCurrencies(currencies => [...currencies, currencyArray]);
}
getCurrentCurrency();
}, [id, currencies.length]);
Which is used for making a new API request when only id change. I need to every time that ID change make a new request with new data. In my case now I have infinite loop. I try to use dependencies but it doesn't work as I expected.
You changing a value (currencies.length), which the useEffect depends on ([id, currencies.length]), on every call.
Therefore you cause an infinite loop.
useEffect(() => {
const getCurrentCurrency = async () => {
// ...
currencyArray.push(euro, dolar / franc, 1 / dolar);
// v The length is changed on every call
setCurrencies(currencies => [...currencies, currencyArray]);
};
getCurrentCurrency();
// v Will called on every length change
}, [id,currencies.length]);
You don't need currencies.length as a dependency when you using a functional useState, currencies => [...currencies, currencyArray]
useEffect(() => {
const getCurrentCurrency = async () => {
...
}
getCurrentCurrency();
}, [id]);
Moreover, as it seems an exchange application, you might one to use an interval for fetching the currency:
useEffect(() => {
const interval = setInterval(getCurrency, 5000);
return () => clearInterval(interval);
}, []);
you can just call the useEffect cb one the component mounted:
useEffect(()=>{
//your code
// no need for checking for the updates it they are inside the component
}, []);

React hook to wait until the previous call is complete

I am working on a react project which uses hooks. And I was assigned a task
"change the useInterval hook, or create a new one (usePoll?). This should operate the same as useInterval, but should wait until the ajax request is complete before starting the timer".
I am new to react hooks and was looking for a solution for this but could not find. Current useInterval function is as follows.
import React, { useEffect, useRef } from 'react';
export function useInterval(callback, delay, immediate = true) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
if (immediate) {
tick();
}
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
and it use in the program as follows.
useInterval(() => {
get(`/api/v1/streams/1`).then(({ data: { data } }) => {
setStream(data);
});
}, 5000);
and I need to change the useInterval function to wait until the ajax request is complete before starting the timer. It would be great if anyone can help me on this. Thanks
Give this a shot.. it requires calling the next function inside of then but it should come close to what you're looking for.
function useInterval(handler, delay, immediate = true) {
React.useEffect(() => {
let interval
const start = () => {
clearInterval(interval)
interval = setInterval(() => handler(start), delay)
}
handler(start)
return () => clearInterval(interval)
}, [])
}
usage:
useInterval((next) => {
get('/api/v1/streams/1').then(data => {
// tell the timer to begin
next()
})
}, 5000)
You can use async\await to await for first call completes.
Modify internal useEffect like so
export function useInterval(callback, delay, immediate = true) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
// useEffect doesn't like async callbacks (https://github.com/facebook/react/issues/14326) so create nested async callback
(async () => {
// Make tick() async
async function tick() {
await savedCallback.current();
}
if (delay !== null) {
if (immediate) {
await tick(); // Here we should await for tick()
}
let id = setInterval(tick, delay); // Unfortunately setInterval is not async/await compatible. So it will not await for tick
return () => clearInterval(id);
}
})(); // Call nested async function
}, [delay]);
}
And you callback should return Promise so async\await to work properly
useInterval(() => {
// After .then promise will be resolved, so our useInterval will know about that
return get(`/api/v1/streams/1`).then(({ data: { data } }) => {
setStream(data);
});
}, 5000);

Request to server each N seconds after flag

I have a specific case. The first thing I do is request the Index.DB. After I got the taskId from it, I need to start asking the server every 5 seconds. And stop doing this on a specific flag. How can i do that properly with hooks?
I'tried to use useInterval hook like this:
https://github.com/donavon/use-interval;
But when i set it in useEffect causes consistent error:
Invalid hook call. Hooks can only be called inside of the body of a function component.
const Page = () => {
const [task, setTask] = useState({})
const isLoaded = (task.status === 'fatal');
const getTask = (uuid: string) => {
fetch(`${TASK_REQUEST_URL}${uuid}`)
.then(res => {
return res.json();
})
.then(json => {
setTask(json.status)
})
.catch(error => console.error(error));
};
useEffect(() => {
Storage.get('taskId')
.then(taskId => {
if (!taskId) {
Router.push('/');
}
useInterval(() => getTask(taskId), 5000, isTaskStatusEqualsSomthing)
})
}, []);
return (
<p>view</p>
);
};
I also tried to play around native setInterval like this
useEffect(() => {
Storage.get('taskId')
.then(taskId => {
if (!taskId) {
Router.push('/');
}
setInterval(() => getTask(taskId), 5000)
})
}, []);
But in this case i don't know how to clearInterval and also code looks dirty.
The solution is simple. You just need to configure your setInterval within .then callback like
useEffect(() => {
let timer;
Storage.get('taskId')
.then(taskId => {
if (!taskId) {
Router.push('/');
else {
timer = setInterval(() => getTask(taskId), 5000)
}
}
})
return () => {clearInterval(timer)}
}, []);
The reason, first approach doesn't work for you is because you cannot call a hook conditionally or in useEffect as you did for useInterval

Resources