Too frequent API fetch with setInterval() in React - javascript

I have a problem with my React project. I have a basic SelectList with milli seconds and based on that I want to set the interval of setInterval. The thing is, it looks like it doesn't change the interval and the API calls seems too fast. The basic value is 1 minute. Here is my code:
useEffect(() => {
FetchData();
const interval = setInterval(() => {
FetchData();
}, Interval)
return() => clearInterval(interval);
},[Interval])
const handleChangeInInterval = (event) =>{
console.log("Changed interval to: ", event.target.value);
setInterval(event.target.value)
}
const FetchData = async () =>{
const resp = await axios.get("http://localhost:8080/stock/getcandle/AAPL/1")
console.log(resp);
const formattedData = formatData(resp.data.candleDataList);
console.log(formattedData);
setStockData(formattedData);
console.log("fetch called");
}

Related

Canceling setTimeout early

I'm working on an audio recording class that either runs for an allotted period of time (such as 5 seconds) or can be stopped early by the user.
I'm using setTimeout to define the recording length, which works. However, I'm having trouble getting setTimeout working with a "stop" button. The error is as follows:
Cannot read properties of null (reading 'stop')
When the startRecording function executes, the handleStopRecording function is called which sets a timer with the "stopRecording" function. If the "stopRecording" function is called before the time elapses (by pressing the "stop" button), the function call that was initially in setTimeout will still execute when the timer expires, causing an error.
I tried fixing this by using clearTimeout, but then the "context" of the original function call is lost and we get the same error:
Cannot read properties of null (reading 'stop')
Unless I'm mistaken, I think this is an issue with closure of the setTimeout function - however I'm not sure how to clear the function early with a stop button and limit recording time.
Thank you in advance!
App.js (React.js)
import AudioRecorder from "./audioRecorder";
const App = () => {
const [recordedNameClipURL, setRecordedNameClipURL] = useState(null);
const [timeoutId, setTimeoutId] = useState(null);
const recorder = new AudioRecorder();
const startRecording = () => {
recorder.start();
handleStopRecording();
};
const handleStopRecording = async () => {
const id = setTimeout(stopRecording, 3000);
setTimeoutId(id);
};
const stopRecording = async () => {
clearTimeout(timeoutId);
const response = await recorder.stop();
setRecordedNameClipURL(response);
};
return (
...
);
};
audioRecorder.js
class AudioRecorder {
constructor() {
this.audioRecorder = null;
this.audioChunks = [];
}
initialize = async () => {
try {
await this.isSupported();
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this.audioRecorder = new MediaRecorder(stream);
this.audioRecorder.addEventListener("dataavailable", event => {
this.audioChunks.push(event.data);
});
} catch (err) {
console.log(err.message);
}
};
start = async () => {
try {
await this.initialize();
this.audioRecorder.start();
} catch (err) {
console.log(err.message);
}
};
stop = async () => {
try {
this.audioRecorder.stop();
const blob = await this.stopStream();
return URL.createObjectURL(blob);
} catch (err) {
console.log(err.message);
}
};
stopStream = () => {
return new Promise(resolve => {
this.audioRecorder.addEventListener("stop", () => {
const audioBlob = new Blob(this.audioChunks, {
type: this.audioRecorder.mimeType,
});
resolve(audioBlob);
});
});
};
isSupported = () => {
return new Promise((resolve, reject) => {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
resolve(true);
}
reject(new Error("getUserMedia not supported on this browser!"));
});
};
}
export default AudioRecorder;
Store the timer inside a React Ref instead
I usually store timeout/interval IDs in a React ref, because storing the handle isn't really "application state" the way that other things are. Sometimes it's needed to avoid render thrashing.
Here's what that looks like:
let timerRef = React.useRef(null)
const handleStopRecording = async () => {
timerRef.current = setTimeout(stopRecording, 3000)
}
const stopRecording = async () => {
clearTimeout(timerRef.current)
timerRef.current = null // good idea to clean up your data
const response = await recorder.stop()
setRecordedNameClipURL(response)
}
Code that needs to know if the timer is running should consult the timerRef; no additional state is needed:
let timerIsRunning = !!timerRef.current
You can try using a boolean value to check if the process is stopped. You can store it in state and change its value when starting or stopping
const [isStopped, setIsStopped] = useState(false);
const handleStopRecording = async () => {
const id = setTimeout(() => {
if(!isStopped){
stopRecording
}
}, 3000);
setTimeoutId(id);
};

Awaiting status update from API with React Hooks (useEffect)?

I am trying to achieve the following:
Using React + React Hooks + useEffect
Make an API post request
Receiving an ID
Making another post request using the ID to get the status (Queued or Completed)
So basically
{
id: "xxxxxxx",
status: "queued"
}
Is what I get back. Now the processing time varies, but I want to periodically check if the status has changed from "queued" to "completed". And while it's not completed, I want to display a loading spinner.
What would be the best way to do this? Would this be possible with promises / async functions? Or do I have to use some kind of interval to re-check the status periodically?
I am basically trying to use this in React: https://docs.assemblyai.com/walkthroughs#authentication
You could do something like this:
// Mocks - after the 2nd attempt getStatus will return "done"
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let attempt = 0;
const getId = async () => {
console.log("getId");
await delay();
return { id: "id1" };
};
const getStatus = async (id) => {
console.log("getStatus", { id, attempt });
attempt += 1;
await delay(1000);
return {
id,
status: attempt < 2 ? "queued" : "done"
};
};
export default function App() {
const [id, setId] = useState();
const [isDone, setIsDone] = useState(false);
useEffect(() => {
const effect = async () => {
const { id } = await getId();
setId(id);
};
effect();
}, []);
useEffect(() => {
let cancelled = false;
const effect = async () => {
let { status } = await getStatus(id);
while (status !== "done") {
if (cancelled) {
return;
}
await delay(1000);
status = (await getStatus(id)).status;
}
setIsDone(true);
};
if (id) {
effect();
}
return () => {
cancelled = true;
};
}, [id]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>{isDone ? "Done" : "Loading..."}</h2>
</div>
);
}
Note: this is simplified and does not cover error scenarios, but shows how this can be put together

Where to put setInterval in react

I need every 5 seconds (actually every 2 minutes, but having 5 seconds while testing) to make a GET request to an API.
This should be done only when the user is logged in.
The data should be displayed by one component that is called "Header".
My first thought was to have an interval inside that component.
So with "useEffect" I had this in the Header.js:
const [data, setData] = useState([]);
useEffect(async () => {
const interval = setInterval(async () => {
if(localStorage.getItem("loggedInUser")){
console.log("Logged in user:");
console.log(new Date());
try{
const data = await getData();
if(data){
setData(data);
}
console.log("data",data);
}
catch(err){
console.log("err",err);
}
}
}, 5000);
return () => clearInterval(interval);
}, []);
Im getting the data. But looking at the console, it does not look so good.
First of all, sometimes I get this:
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.
It seems to happen when I go to other pages that have been called with a "href" (so without using for example history to change page).
Also, probably because of the thing above, after a while that I browse the pages, I see that the interval is not every 5 seconds anymore. It happens 2-3 times every 5 seconds. Maybe every seconds.
To me this does not look good at all.
Im wondering first of all if this component is the right place to put the interval in.
Maybe the interval should be in App.js? But then I would need a mechanism to pass data to children component (the Header.js, maybe could do this with Context).
As Sergio stated in his answer on async actions you should aways check if the component is currently mounted before doing side effects.
However using a variable that is initialised within the component won't work as it is being destroyed on unmount. You should rather use useRef hook as it will persist between component mounts.
const [data, setData] = useState([]);
const mountedRef = useRef(null);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
useEffect(async () => {
const interval = setInterval(async () => {
if (localStorage.getItem("loggedInUser")){
console.log("Logged in user:", new Date());
try {
const data = await getData();
if (data && mountedRef.current) {
setData(data);
console.log("data", data);
}
} catch(err) {
if (mountedRef.current) console.log("err", err);
}
}
}, 5000);
return () => clearInterval(interval);
}, []);
Or alternatively declare a boolean variable in the module scope so that its not freed by the garbage collection process
You can create a isMounted variable to control if the async task happens after the component is unmounted.
As the error said, you are trying to update component state after it's unmounted, so you should "protect" the setData call.
const [data, setData] = useState([]);
useEffect(async () => {
let isMounted = true;
const interval = setInterval(async () => {
if(localStorage.getItem("loggedInUser")){
console.log("Logged in user:");
console.log(new Date());
try{
const data = await getData();
if(data && isMounted){
setData(data);
}
console.log("data",data);
}
catch(err){
console.log("err",err);
}
}
}, 5000);
return () => {
clearInterval(interval);
isMounted = false;
}
}, []);

I need to fix setInterval so that it only runs my code once every interval - currently runs too many times

I need to run a function to request data from my API every 60 seconds. The set interval runs every 60 seconds, however it runs the fetch fixtures function multiple times.
Below is the code for my component minus the redux prop and action mapping, and the actual content of the component. Everything works fine besides this setInterval call. Any ideas?
const LiveContest = (props) => {
//function to fetch fixtures
const fetchFixtures = async() => {
let leagueIds = props.leagues.map(league=>{
return league.intLeagueID
});
if(leagueIds.length > 0 & props.sports.length > 0){
//api parameters to get fixtures
let body = {
leagueIDs: leagueIds,
start:props.contest.dtmStart,
end: props.contest.dtmEnd
}
const url ='urlplaceholder'
const options = {
method:'POST',
headers:{
'Content-Type': 'application/json;charset=UTF-8'
},
body: JSON.stringify(body)
}
//call api
const response = await fetch(url, options);
const fixts = await response.json();
//organize fixtures and set upcoming + inplay
let book = organizeUpcomingBook(fixts, props.leagues, props.sports);
if(_.isEqual(props.book, book) === false){
props.setBook(book);
}
setDisplay(true);
}
}
//check book once a minute and remove live games
setInterval(function(){
if(!isEmpty(props.contest)){
console.log('ran once')
fetchFixtures();
}
}, 60000);
return (
<>
</>
)
export default connect(mapStateToProps, mapDispatchToProps)(LiveContest);
Code writed within a functional component is executed every rendering.
This means that the LiveContest component runs setInterval every rendering, which seems to result in multiple data fetches.
Consider using useEffect. here is the API reperence.
Below is example one.
const LiveContest = (props) => {
// ...
useEffect(() => {
const timerId = setInterval(function(){
if(!isEmpty(props.contest)){
console.log('ran once')
fetchFixtures();
}
}, 60000);
return () => {
clearInterval(timerId);
};
}, []);
// ...
return ...
}

How delay redux saga action to store data update?

Can't delay action firing (initialize from redux-form) to store data update after fetching.
Store is initializing with empty account object.
At initial render getAccount action firing and triggering update of store.
useEffect see store updating and triggering getAccount action second time
second data request
END
const {getAccount, initialize} = props
prepareData = () => {...prepared obj}
useEffect(() => {
const begin = async () => {
await getAccount();
await initialize(prepareData());
};
begin();
}, [account.id]);
main aim to avoid unnecessary second request
What if you put a conditional on the begin call?
const {getAccount, initialize} = props
const {begun, setBegun} = useState(false)
prepareData = () => {...prepared obj}
useEffect(() => {
const begin = async () => {
await setBegun(true);
await getAccount();
await initialize(prepareData());
};
begun || begin();
}, [account.id]);
Should getAccount be called if account.id doesn't exist? If not then simply check that account.id exists before calling begin.
const {getAccount, initialize} = props
prepareData = () => {...prepared obj}
useEffect(() => {
// add a guard condition to exit early if account.id doesn't exist yet
if (!account.id) {
return;
}
const begin = async () => {
await getAccount();
await initialize(prepareData());
};
begin();
}, [account.id]);

Categories