Making an API request every X amount of seconds in reactJS - reactjs

On entering CurrencyPage, I am trying to get the list of currencyConversionRates from database and then subsequently set an interval to constantly get the new updated list of currencyConversionRates every 6 seconds.
Up till this point, everything works fine. However, I am unable to pass the value of the setInterval to the cleanup function. The value being returned is always in the format of a promise
PromiseĀ {<fulfilled>: 1}
How can I pass the setInterval value to the cleanup function so that the interval stops being executed upon navigating to other pages
Use Effect
useEffect(() => {
const getCurrencyConversionRates=async()=>{
console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
//Gets new information every 6 seconds
const intervalCall = setInterval(async() => {
console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
return intervalCall;
}
let intervalCall = getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
console.log(intervalCall);
clearInterval(intervalCall);
};
},[]);
Solution Based on Azzy's answer
Rather than to try and return the interval in getCurrencyConversionRates(), a simple alternative is to just initialize a variable to store the setInterval as it is created within the function.
Note to self -> It is not possible to pass a variable by reference through getCurrencyConversionRates() as a parameter as changes made to the variable wont be saved. However, in JS you can simply initialize the variable outside of the function and modify it's value from within the function.
https://www.javascripttutorial.net/javascript-pass-by-value/
useEffect(() => {
const getCurrencyConversionRates=async()=>{
// console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
//Gets new information every 6 seconds
intervalCall = setInterval(async() => {
// console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
// return intervalCall;
}
let intervalCall;
getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
console.log(intervalCall);
clearInterval(intervalCall);
};
},[]);

This may help
useEffect(() => {
let isValid = true;
let intervalCall;
const getCurrencyConversionRates = async () => {
console.log("Retrieving list of currency conversion rate");
await retrieveCurrencyConversionRateFunction();
// after await if the component unmounts, and this scope
// becomes stale, skip futher execution
// so the interval wont be started, and wont break in dev mode where useEffect runs twice
if (!isValid) { return; }
//Gets new information every 6 seconds
intervalCall = setInterval( async () => {
console.log("Getting new currency conversion rates!");
const currTime = new Date().toLocaleTimeString();
console.log("Time -> "+currTime);
await retrieveCurrencyConversionRateFunction();
// might want to check valid scope inside
// retrieveCurrencyConversionRateFunction
// by passing it a flag to skip execution on a unmounted component scope
}, 60*100);
//Have to wait until currencyConversionRateFunction is loaded before the map function is called on it in the render view otherwise will have error
setLoading(false);
}
getCurrencyConversionRates();
//Clear interval
return () => {
console.log("CLEARING CURRENCY INTERVAL HERE");
isValid = false
// if interval was not stared, dont clear it
if (intervalCall) { clearInterval(intervalCall); }
console.log(intervalCall);
};
},[]);
more about useEffect life cycle, to get an idea why
A new effect is created after every render
How the cleanup for previous effect occurs before executing of current useEffect
You can read about why isValid is set synchronizing with effects
If you are intererested in taking a deep dive, consider reading a blog post by Dan on useEffect, its old but explanation the details to build a good mental model about useEffects and functional components.
Hope it helps, cheers

Related

The useEffect won't change the state LIVE when clicked but content does change on reload

I'm trying to change my page's content when clicking on the link but the thing is it only changes on reload and not directly when clicked.
function Home( { category } ) {
const [news, setNews] = useState([]);
useEffect(() => {
async function newsArticles(){
const request = await axios.get(category);
console.log(request.data.articles);
setNews(request.data.articles);
return request;
}
newsArticles();
}, [category]);
return (
<div className='home'>
{news.map(ns => (
<NewsCard className='homeNewsCard' key = {ns.source.id} title = {ns.title} description = {ns.description} image = {ns.urlToImage} link = {ns.url}/>
))}
</div>
)
}
Since the effect captures the values at the time the component is rendered, any asynchronous code that executed in the effect will also see these values rather than the latest values. Example :
useEffect(()=>{
asyncFunc(props.val)
.then((res)=> setData(res + props.oth))
},[props.val]);
props.oth is used when the promise resolves, but the effecct captures the values of props.val and prips.oth at the time of render , the value of props.oth had changed during the time it took for the promise to reoslve , the code would see the old value
Solution :
you can either use reference to store the value of props.oth so your code will always see the latest value or you can add props.oth back to dependency array and return a cleanup function .
Example :
useEffect(()=>{
let cncl = false
asyncFunc(props.val)
.then((res)=>{
if(!cncl)
setData(res + props.oth)})
return ()=> cncl = true}
,[props.val, props.oth])
cncl flag is raised before running the effecct again (when props.val or props.oth have changes comapred with the previous render).

how to get updated state value after updating in a function

I'm trying to get login user detail. I need to put data to state and used the same state for further processing it takes time to put data to state and use this data. nearly it takes 2- 3 sec to use the state with that data.
I solved this issue if we use setTimeOut() function with 3 sec so that we it updates data in this time. if we don't use setTimeOut() and use state wright after updating it then it will provide initial data of the state.
complete_date = date + ":" + month + ":" + year;
var query = firebase.database().ref('attendence/'+ complete_date +"/").orderByKey();
email_match=false;
entry_time =null,
l_id = null,
query.on("value",(data)=>
{
data.forEach(function(childs)
{
l_id = childs.key;
// __ to check if the user already loged in before.
var att_query = firebase.database().ref('attendence/'+ complete_date +"/"+ l_id).orderByKey();
att_query.once("value",(data)=>
{
if(email == data.child('email').val())
{
email_match = true;
entry_time = data.child('timeEntry').val();
}
}); //
}); //
this.setState({ last_id:l_id });
this.setState({ emailMatchState:email_match });
this.setState({alreayLogedState : entry_time});
}); // _____end of query.on("value",(data)=>
setTimeout(() =>
{
console.log("-----------------------------------");
console.log("already logged :",this.state.alreayLogedState," email match :",this.state.emailMatchState , " result : ", this.state.result , " last_id :",st.last_id);
console.log("-----------------------------------");
},3000);
I need to use state after updating without the use of setTimeout() for faster working of the application.
The second argument for setState is a callback to execute when all updates are done
this.setState({ last_id:l_id }, () => console.log(this.state.last_id))
setState takes an optional second argument that gets called after the updates to the state object have taken place.
example:
let callback = () => {
// do something
}
this.setState({
keyToUpdate: valueToUpdate
}, callback)
Additional reading about the use of this callback:
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296?gi=18aa91e88437
Also, unless something is happening in between the seperate calls, these:
this.setState({ last_id:l_id });
this.setState({ emailMatchState:email_match });
this.setState({alreayLogedState : entry_time});
can be simplified to this:
this.setState({
last_id:l_id,
emailMatchState: email_match,
alreayLogedState: entry_time
}); //optionally add callback

Can not change state value before submitting in reactjs

I am using one of react phone input library and getting the value of phone number is in "+9901231231234" format . I want only last ten value is in integer form. So i am converting them in number.
if (this.validator.allValid()) {
const phnNo = this.state.phone // +9901231231234
if(phnNo.length>10){
var lastTen = phnNo.substr(phnNo.length - 10);
const subphone = Number(lastTen) //1231231234
this.setState({ phone: subphone }) // Now I want to set the this.state.phone = 1231231234
alert(this.state.phone) // +9901231231234
}
} else {
this.validator.showMessages();
// rerender to show messages for the first time
// you can use the autoForceUpdate option to do this automatically`
this.forceUpdate();
}
But I can not change the current state value with the new value i am generating.Thank you.
The setState function will not update your values synchronously, your state will not be changed right away. The second parameter of it allows you to give a callback function triggering when the state has been mutated :
this.setState({ phone: subphone }, () => { alert(this.state.phone) //1231231234 })

React hook rendering an extra time

My code is causing an unexpected amount of re-renders.
function App() {
const [isOn, setIsOn] = useState(false)
const [timer, setTimer] = useState(0)
console.log('re-rendered', timer)
useEffect(() => {
let interval
if (isOn) {
interval = setInterval(() => setTimer(timer + 1), 1000)
}
return () => clearInterval(interval)
}, [isOn])
return (
<div>
{timer}
{!isOn && (
<button type="button" onClick={() => setIsOn(true)}>
Start
</button>
)}
{isOn && (
<button type="button" onClick={() => setIsOn(false)}>
Stop
</button>
)}
</div>
);
}
Note the console.log on line 4. What I expected is the following to be logged out:
re-rendered 0
re-rendered 0
re-rendered 1
The first log is for the initial render. The second log is for the re-render when the "isOn" state changes via the button click. The third log is when setInterval calls setTimer so it's re-rendered again. Here is what I actually get:
re-rendered 0
re-rendered 0
re-rendered 1
re-rendered 1
I can't figure out why there is a fourth log. Here's a link to a REPL of it:
https://codesandbox.io/s/kx393n58r7
***Just to clarify, I know the solution is to use setTimer(timer => timer + 1), but I would like to know why the code above causes a fourth render.
The function with the bulk of what happens when you call the setter returned by useState is dispatchAction within ReactFiberHooks.js (currently starting at line 1009).
The block of code that checks to see if the state has changed (and potentially skips the re-render if it has not changed) is currently surrounded by the following condition:
if (
fiber.expirationTime === NoWork &&
(alternate === null || alternate.expirationTime === NoWork)
) {
My assumption on seeing this was that this condition evaluated to false after the second setTimer call. To verify this, I copied the development CDN React files and added some console logs to the dispatchAction function:
function dispatchAction(fiber, queue, action) {
!(numberOfReRenders < RE_RENDER_LIMIT) ? invariant(false, 'Too many re-renders. React limits the number of renders to prevent an infinite loop.') : void 0;
{
!(arguments.length <= 3) ? warning$1(false, "State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().') : void 0;
}
console.log("dispatchAction1");
var alternate = fiber.alternate;
if (fiber === currentlyRenderingFiber$1 || alternate !== null && alternate === currentlyRenderingFiber$1) {
// This is a render phase update. Stash it in a lazily-created map of
// queue -> linked list of updates. After this render pass, we'll restart
// and apply the stashed updates on top of the work-in-progress hook.
didScheduleRenderPhaseUpdate = true;
var update = {
expirationTime: renderExpirationTime,
action: action,
eagerReducer: null,
eagerState: null,
next: null
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
var firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
var lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
flushPassiveEffects();
console.log("dispatchAction2");
var currentTime = requestCurrentTime();
var _expirationTime = computeExpirationForFiber(currentTime, fiber);
var _update2 = {
expirationTime: _expirationTime,
action: action,
eagerReducer: null,
eagerState: null,
next: null
};
// Append the update to the end of the list.
var _last = queue.last;
if (_last === null) {
// This is the first update. Create a circular list.
_update2.next = _update2;
} else {
var first = _last.next;
if (first !== null) {
// Still circular.
_update2.next = first;
}
_last.next = _update2;
}
queue.last = _update2;
console.log("expiration: " + fiber.expirationTime);
if (alternate) {
console.log("alternate expiration: " + alternate.expirationTime);
}
if (fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork)) {
console.log("dispatchAction3");
// The queue is currently empty, which means we can eagerly compute the
// next state before entering the render phase. If the new state is the
// same as the current state, we may be able to bail out entirely.
var _eagerReducer = queue.eagerReducer;
if (_eagerReducer !== null) {
var prevDispatcher = void 0;
{
prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
var currentState = queue.eagerState;
var _eagerState = _eagerReducer(currentState, action);
// Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
_update2.eagerReducer = _eagerReducer;
_update2.eagerState = _eagerState;
if (is(_eagerState, currentState)) {
// Fast path. We can bail out without scheduling React to re-render.
// It's still possible that we'll need to rebase this update later,
// if the component re-renders for a different reason and by that
// time the reducer has changed.
return;
}
} catch (error) {
// Suppress the error. It will throw again in the render phase.
} finally {
{
ReactCurrentDispatcher$1.current = prevDispatcher;
}
}
}
}
{
if (shouldWarnForUnbatchedSetState === true) {
warnIfNotCurrentlyBatchingInDev(fiber);
}
}
scheduleWork(fiber, _expirationTime);
}
}
and here's the console output with some additional comments for clarity:
re-rendered 0 // initial render
dispatchAction1 // setIsOn
dispatchAction2
expiration: 0
dispatchAction3
re-rendered 0
dispatchAction1 // first call to setTimer
dispatchAction2
expiration: 1073741823
alternate expiration: 0
re-rendered 1
dispatchAction1 // second call to setTimer
dispatchAction2
expiration: 0
alternate expiration: 1073741823
re-rendered 1
dispatchAction1 // third and subsequent calls to setTimer all look like this
dispatchAction2
expiration: 0
alternate expiration: 0
dispatchAction3
NoWork has a value of zero. You can see that the first log of fiber.expirationTime after setTimer has a non-zero value. In the logs from the second setTimer call, that fiber.expirationTime has been moved to alternate.expirationTime still preventing the state comparison so re-render will be unconditional. After that, both the fiber and alternate expiration times are 0 (NoWork) and then it does the state comparison and avoids a re-render.
This description of the React Fiber Architecture is a good starting point for trying to understand the purpose of expirationTime.
The most relevant portions of the source code for understanding it are:
ReactFiberExpirationTime.js
ReactFiberScheduler.js
I believe the expiration times are mainly relevant for concurrent mode which is not yet enabled by default. The expiration time indicates the point in time after which React will force a commit of the work at the earliest opportunity. Prior to that point in time, React may choose to batch updates. Some updates (such as from user interactions) have a very short (high priority) expiration, and other updates (such as from async code after a fetch completes) have a longer (low priority) expiration. The updates triggered by setTimer from within the setInterval callback would fall in the low priority category and could potentially be batched (if concurrent mode were enabled). Since there is the possibility of that work having been batched or potentially discarded, React queues a re-render unconditionally (even when the state is unchanged since the previous update) if the previous update had an expirationTime.
You can see my answer here to learn a bit more about finding your way through the React code to get to this dispatchAction function.
For others who want to do some digging of their own, here's a CodeSandbox with my modified version of React:
The react files are modified copies of these files:
https://unpkg.com/react#16/umd/react.development.js
https://unpkg.com/react-dom#16/umd/react-dom.development.js

Last item in array won't update with ajax request

I'm building a store dashboard in Angular 5 which shows orders that are in route and orders that have arrived. When a user clicks a button it sets the hasArrived field in the order as true. With the setInterval funciton in the store dashboard component I'm making an API call every second to get the order data and check the filed hasArrived to see if it's true or false. In the dashboard you can see orders move from inRoute to arrived in seemingly real time once this button is clicked. The only problem is when there's only a single order in the inRoute array, even when hasArrived is switched to true, it still displays on the screen in the inRoute orders. Only once you refresh the page does it update into the arrived section.
setInterval(() =>{
this.storeService.getStore(localStorage.getItem('storeId'))
.subscribe((data) => {
console.log(data);
this.store = data;
let ir = [];
let ar = [];
for(let order of data.orders){
if(order.completedPurchase === false){
if(order.hasArrived === false){
ir.push(order);
this.inRoute = ir;
}
if(order.hasArrived == true){
ar.push(order);
this.here = ar;
}
}
}
}),
error => console.log(error);
}, 1000);
try the following code:
setInterval(() => {
this.storeService.getStore(localStorage.getItem('storeId'))
.subscribe((data) => {
console.log(data);
this.store = data; // do you need this?
this.inRoute = [];
this.here = [];
(data.orders || []).forEach((order) => {
if (!order.completedPurchase) {
if (order.hasArrived) {
this.here.push(order);
} else {
this.inRoute.push(order);
}
}
});
}),
error => console.log(error);
}, 1000);
In short, I guess the root cause is that you do not reassign this.inRoute when its single item changes hasArrived from false to true.
I have tried to keep as much untouched as I could, but there some areas to think about:
try use setTimeout: fetch orders every second after the previous data arrived
do not forget to clean-up: call either clearInterval/clearTimeout when the component is being destroyed
I guess there could be a solution with rx-js, but I am not familiar with it..

Resources