React polling is generally as below
useEffect(() => {
const boo = async () => {
do something
}
let timer = setInterval(boo, 3000);
return () => {
clearInterval(timer);
}
}, []);
So above code will do something every 3 seconds.
My question is can I have boo() run immediately then start polling?
You can try the following to trigger immediately after the component renders.
useEffect(() => {
const boo = () => {
do something
}
boo(); //will make sure it is called immediately without the timer
let timer = setInterval(boo, 3000);
return () => {
clearInterval(timer);
}
}, []) // [] will make sure it only happens on the first time
I like to just return the function if I want it to run immediately, I feel like it is the least verbose method:
useEffect(() => {
const boo = () => {
do something
return boo;
}
let timer = setInterval(boo(), 3000);
return () => {
clearInterval(timer);
}
}
UPDATE
import React, { useEffect, useRef } from 'react';
export default function App() {
const timer = useRef(null);
useEffect(() => {
const myFunc = async () => {
const boo = async () => {
console.log('boo');
// do something
};
await boo();
timer.current = setInterval(boo, 3000);
};
myFunc();
return () => {
if (timer) {
clearInterval(timer.current);
}
};
}, [timer]);
return <div />;
}
Related
I created this hook that is responsible to do something after an amount of time:
const useTime = (callback, timeout = 1000) => {
useEffect(() => {
const timer = setTimeout(() => {
callback()
}, timeout);
return () => clearTimeout(timer);
}, []);
}
The hook is working, but i can not call it inside a method like:
{
clear: () => {
useTime(() => console.log('close'), 6000 )
},
... this is happen because of hooks rules. Question: How to refactor the hook to be able to call it inside a method or a function?
You probably need to do like this -
function useTime(cb, timeout = 100) {
const timer = setTimeout(() => {
cb();
}, timeout);
return () => clearTimeout(timer);
}
function anotherMethod() {
const cancel = useTime(runJob, 1000);
// if you wanna cancel the timer, just call the cancel function
cancel();
}
You can try something around this:
const useTime = () => {
const timer = useRef();
const fn = useCallback((callback, timeout = 1000) => {
timer.current = setTimeout(() => {
callback()
}, timeout);
}, []);
useEffect(() => {
return () => clearTimeout(timer.current);
}, []);
return fn;
}
const delayedFn = useTime();
clear: () => {
delayedFn(() => console.log('close'), 6000)
},
That's the warning in the console,
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Here is my code
const [index, setIndex] = useState(0);
const [refreshing, setRefreshing] = useState(false);
const refContainer: any = useRef();
const [selectedIndex, setSelectedIndex] = useState(0);
const navigation = useNavigation();
useEffect(() => {
refContainer.current.scrollToIndex({animated: true, index});
}, [index]);
const theNext = (index: number) => {
if (index < departments.length - 1) {
setIndex(index + 1);
setSelectedIndex(index + 1);
}
};
setTimeout(() => {
theNext(index);
if (index === departments.length - 1) {
setIndex(0);
setSelectedIndex(0);
}
}, 4000);
const onRefresh = () => {
if (refreshing === false) {
setRefreshing(true);
setTimeout(() => {
setRefreshing(false);
}, 2000);
}
};
What should I do to make clean up?
I tried to do many things but the warning doesn't disappear
setTimeout need to use in useEffect instead. And add clear timeout in return
useEffect(() => {
const timeOut = setTimeout(() => {
theNext(index);
if (index === departments.length - 1) {
setIndex(0);
setSelectedIndex(0);
}
}, 4000);
return () => {
if (timeOut) {
clearTimeout(timeOut);
}
};
}, []);
Here is a simple solution. first of all, you have to remove all the timers like this.
useEffect(() => {
return () => remover timers here ;
},[])
and put this
import React, { useEffect,useRef, useState } from 'react'
const Example = () => {
const isScreenMounted = useRef(true)
useEffect(() => {
isScreenMounted.current = true
return () => isScreenMounted.current = false
},[])
const somefunction = () => {
// put this statement before every state update and you will never get that earrning
if(!isScreenMounted.current) return;
/// put here state update function
}
return null
}
export default Example;
I have this code which updates the state count every 1 seconds.
How can I access the value of the state object in setInterval() ?
import React, {useState, useEffect, useCallback} from 'react';
import axios from 'axios';
export default function Timer({objectId}) {
const [object, setObject] = useState({increment: 1});
const [count, setCount] = useState(0);
useEffect(() => {
callAPI(); // update state.object.increment
const timer = setInterval(() => {
setCount(count => count + object.increment); // update state.count with state.object.increment
}, 1000);
return () => clearTimeout(timer); // Help to eliminate the potential of stacking timeouts and causing an error
}, [objectId]); // ensure this calls only once the API
const callAPI = async () => {
return await axios
.get(`/get-object/${objectId}`)
.then(response => {
setObject(response.data);
})
};
return (
<div>{count}</div>
)
}
The only solution I found is this :
// Only this seems to work
const timer = setInterval(() => {
let increment = null;
setObject(object => { increment=object.increment; return object;}); // huge hack to get the value of the 2nd state
setCount(count => count + increment);
}, 1000);
In your interval you have closures on object.increment, you should use useRef instead:
const objectRef = useRef({ increment: 1 });
useEffect(() => {
const callAPI = async () => {
return await axios.get(`/get-object/${objectId}`).then((response) => {
objectRef.current.increment = response.data;
});
};
callAPI();
const timer = setInterval(() => {
setCount((count) => count + objectRef.current);
}, 1000);
return () => {
clearTimeout(timer);
};
}, [objectId]);
I have the following react class component to call an API every 10 seconds. Its works with no issues.
class Alerts extends Component {
constructor() {
this.state = {
alerts: {},
}
}
componentDidMount() {
this.getAlerts()
this.timerId = setInterval(() => this.getAlerts(), 10000)
}
componentWillUnmount() {
clearInterval(this.timerId)
}
getAlerts() {
fetch(this.getEndpoint('api/alerts/all"))
.then(result => result.json())
.then(result => this.setState({ alerts: result }))
}
render() {
return (
<>
<ListAlerts alerts={this.state.alerts} />
</>
)
}
}
I am trying covert this to a react functional component. This is my attempt so far.
const Alerts = () => {
const [alerts, setAlerts] = useState([])
useEffect(() => {
getAlerts()
setInterval(() => getAlerts(), 10000)
}, [])
getAlerts() {
fetch(this.getEndpoint('api/alerts/all"))
.then(result => result.json())
.then(result => setAlerts(result)
}
return (
<>
<ListAlerts alerts={alerts} />
</>
)
}
Please can someone help me complete the example? Is useEffect the correct usage or is there a better option?
Any help would be appreciated
One issue here is that this.getEndpoint will not work from a function component. It seems the original Alerts class component is missing some code since that must be implemented somewhere.
Another issue is that the interval is not being cleaned up - you should return a cleanup function from the effect body to clear the timer.
Lastly there's no reason to re-define getAlerts on every render, defining it once inside of the effect body would be better.
After cleaning up some missing parens, etc. my final implementation would look something like:
function getEndpoint(path) {
return ...; // finish implementing this
}
const Alerts = () => {
const [alerts, setAlerts] = useState([])
useEffect(() => {
function getAlerts() {
fetch(getEndpoint('api/alerts/all'))
.then(result => result.json())
.then(result => setAlerts(result))
}
getAlerts()
const interval = setInterval(() => getAlerts(), 10000)
return () => {
clearInterval(interval);
}
}, [])
return (
<>
<ListAlerts alerts={alerts} />
</>
)
}
I found this blog by Dan Abramov which explains the idea of a useInterval hook that solves this problem.
You can use it like this :
function Counter() {
useInterval(() => {
callMyApi()
}, 1000);
}
And declare the useInterval hook this way :
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
Hope it helps someone!
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);