RequestAnimationFrame won't stop logging - javascript

let stopId;
const playVideo = () =>{
if(isPlaying){
videoRef.current.pause()
window.cancelAnimationFrame(stopId)
}else{
videoRef.current.play();
window.requestAnimationFrame(calcTime)
}
setIsPlaying(!isPlaying)
}
const calcTime = () =>{
stopId = window.requestAnimationFrame(calcTime)
console.log('hey')
}
This is for the seeker animation for a video player. I was console logging something when I noticed that it wouldn't stop logging despite pausing the video. The seeker animation stopped but the logging is still going.
Another thing I tried is:
const calcTime = () =>{
stopId = window.requestAnimationFrame(calcTime)
console.log('hey')
setTimeout(() => {
videoRef.current.pause()
window.cancelAnimationFrame(stopId)
console.log('pause')
}, 5000);
}
This would log 'hey' 300 times before stopping and then log 'paused' 300 times before stopping too. I'm so confused on what is happening. Does this mean that the request is still happening when the video is paused and is there going to be a performance issue?

I see my problem now. Since I'm using React, the stopId is getting nuked whenever the state changes. So I attached a useRef to it and set stopId.current = window.requestAnimationFrame(calcTime) and now it properly stops

Related

Violation: 'setTimeout' handler took XXms

Expected behaviour: When i am on a page for x amount of time, once that time expires when i press back on the window i am redirected to a custom page of my choice, in this case google.
My code works however i noticed in the console i am getting a log that says "[Violation] 'setTimeout' handler took XXms"
Why does this happen? Is there another way to write what i am trying to achieve that is more performant perhaps using new Date() ?
let redirect = false;
const ref = setTimeout(() => {
redirect = true;
console.log('history');
}, 5000);
addEventListener('popstate', (event) => {
if (redirect) {
clearTimeout(ref);
event.preventDefault();
window.location.href = "https://www.google.com/"
}
});

JavaScript async remove firebase document

I have a function that checks if bullet hits a player, and if so it should remove the bullet and the player that got hit.
I have check_if_bullet_hit_player(bulletRef) function that gets a bullet ref, and the id of the player got shot.
I also have a shoot() function that calls the check_if_bullet_hit_player(bulletRef) for every bullet's movement.
Currently when a bullet hits a player, the bullet disappear, but I can't make the player got hit to disappear also.
Attempt - 1:
I tried the following shoot function:
function shoot(speed=0.5, distance=5, targetX, targetY){
var shoot = setInterval(function() {
check_if_bullet_hit_player(bulletRef).then(player_got_shot_id => {
if (player_got_shot_id != ""){
clearInterval(shoot);
bulletRef.remove();
hitPlayerRef = firebase.database().ref(`players/${player_got_shot_id}`);
hitPlayerRef.once("value").then(function(){
alert("hi");
hitPlayerRef.remove();
})
}
})
})
}
I get alert hi when a player gets hit, and the player that gets hit disappear for a moment, then re-created (which is not what is wanted). I think that the player get re-created because of async, so I tried different methods.
Attempt - 2:
I tried as the following stack overflow's answer, which waits for the ref to return:
function shoot(speed=0.5, distance=5, targetX, targetY){
var shoot = setInterval(function() {
check_if_bullet_hit_player(bulletRef).then(player_got_shot_id => {
if (player_got_shot_id != ""){
clearInterval(shoot);
bulletRef.remove();
await firebase.database().ref(`players/${player_got_shot_id}`).remove();
}
})
})
}
and I get:
Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
Attempt - 3:
I tried to wait with then:
function shoot(speed=0.5, distance=5, targetX, targetY){
var shoot = setInterval(function() {
check_if_bullet_hit_player(bulletRef).then(player_got_shot_id => {
if (player_got_shot_id != ""){
clearInterval(shoot);
bulletRef.remove();
hitPlayerRef = firebase.database().ref(`players/${player_got_shot_id}`);
return hitPlayerRef.get(); // HERE we chain the promise
}
}).then(hola => {
hola.remove();
})
})
}
But nothing happens.
How can I make the player got hit to disappear?

React state disappearing

I have a react app that uses the MS Graph API (so it's a bit difficult to post a minimal reproducible example). It has a state variable called chats that is designed to hold the result of fetching a list of chats from the graph API. I have to poll the API frequently to get new chats.
I query the chats endpoint, build an array of newChats and then setChats. I then set a timeout that refreshes the data every 10 seconds (it checks for premature invocation through the timestamp property stored in the state). If the component is unmounted, a flag is set, live (useRef), which stops the refresh process. Each chat object is then rendered by the Chat component (not shown).
Here's the code (I've edited by hand here to remove some irrelevant bits around styles and event propagation so it's possible that typo's have crept in -- it compiles and runs in reality).
const Chats = () => {
const [chats, setChats] = useState({ chats: [], timestamp: 0 });
const live = useRef(true);
const fetchChats = () => {
if (live.current && Date.now() - chats.timestamp < 9000) return;
fetchData(`${baseBeta}/me/chats`).then(res => {
if (res.value.length === chats.chats.length) return;
const chatIds = chats.chats.map(chat => chat.id);
const newChats = res.value.filter(chat => !chatIds.includes(chat.id));
if (newChats.length > 0) {
setChats(c => ({ chats: [...c.chats, ...newChats], timestamp: Date.now() }));
}
setTimeout(fetchChats, 10000);
});
};
useEffect(() => {
fetchChats();
return () => (live.current = false);
}, [chats]);
return (
<div>
{chats.chats.map(chat => (
<Chat chat={chat} />
))}
</div>
);
};
The Chat component must also make some async calls for data before it is rendered.
This code works, for a second or two. I see the Chat component rendered on the screen with the correct details (chat member names, avatars, etc.), but almost before it has completed rendering I see the list elements being removed, apparently one at a time, though that could just be the way its rendered -- it could be all at once. The list collapses on the screen, showing that the chat state has been cleared out. I don't know why this is happening.
I've stepped through the code in the debugger and I can see the newChats array being populated. I can see the setChats call happen. If I put a breakpoint on that line then it is only invoked once and that's the only line that sets that particular state.
So, what's going on? I'm pretty sure React isn't broken. I've used it before without much trouble. What's changed recently is the inclusion of the refresh code. I'm suspicious that the reset is taking away the state. My understanding is that the fetchChats method will be rendered every time the chats state changes and so should see the current value of the chats state. Just in case this wasn't happening, I passed the chats state from the useEffect like this:
useEffect(() => {
fetchChats(chats);
return () => (live.current = false);
}, [chats]);
With the necessary changes in fetchChats to make this work as expected. I get the same result, the chats state is lost after a few seconds.
Edit
Still Broken:
After #Aleks answer my useEffect now looks like this:
useEffect(() => {
let cancel = null;
let live = true;
const fetchChats = () => {
if (Date.now() - chats.timestamp < 9000) return;
fetchData(`${baseBeta}/me/chats`).then(res => {
if (res.value.length === chats.chats.length) return;
const chatIds = chats.chats.map(chat => chat.id);
const newChats = res.value.filter(chat => chat.chatType === "oneOnOne" && !chatIds.includes(chat.id));
if (newChats.length > 0 && live) {
setChats(c => ({ chats: [...c.chats, ...newChats], timestamp: Date.now() }));
}
cancel = setTimeout(fetchChats, 10000);
});
};
fetchChats();
return () => {
live = false;
cancel?.();
};
}, []);
The result of this is that the chats are loaded, cleared, and loaded again, repeatedly. This is better, at least they're reloading now, whereas previously they would disappear forever. They are reloaded every 10 seconds, and cleared out almost immediately still.
Eventually, probably due to random timings in the async calls, the entries in the list are duplicated and the 2nd copy starts being removed immediately instead of the first copy.
There are multiple problems. First this
setTimeout(fetchChats, 10000); will trigger
useEffect(() => {
fetchChats(chats);
return () => (live.current = false);
}, [chats])
You will get 2 fetches one after another.
But the bug you're seeing is because of this
return () => (live.current = false);
On second useEffect trigger, clean up function above with run and live.current will be forever false from now on.
And as Nikki9696 said you you need to clear Timeout in clean up function
The easiest fix to this is, probably
useEffect(() => {
let cancel = null;
let live = true;
const fetchChats = () => {
// not needed
//if ( Date.now() - chats.timestamp < 9000) return;
fetchData(`${baseBeta}/me/chats`).then(res => {
//this line is not needed
//if (res.value.length === chats.chats.length) return;
// remove all the filtering, it can be done elsewhere where
// you can access fresh chat state
//const chatIds = chats.chats.map(chat => chat.id);
//const newChats = res.value.filter(chat =>
//!chatIds.includes(chat.id));
if (res.value?.length > 0&&live) {
setChats(c => ({ chats: [...c.chats, ...res.value], timestamp: Date.now() }));
cancel = setTimeout(fetchChats, 10000);
}
});
};
fetchChats()
return () => { live=false; if(cancel)window.clearTimeout(cancel) };
}, []);
Edit: typo cancel?.() to window.clearTimeout(cancel);
Ok, I have an idea what's happening and how to fix it. I am still not sure why it is behaving like this, so please comment if you understand it better than me.
Basically, for some reason I don't understand, the function fetchChats only ever sees the initial state of chats. I am making the mistake of filtering my newly fetched list against this state, in which the array is empty.
If I change my useEffect code to do this instead:
setChats(c => {
return {
chats: [
...c.chats,
...res.value.filter(cc => {
const a = c.chats.map(chat => chat.id);
return !a.includes(cc.id);
})
],
timestamp: Date.now()
};
});
Then my filter is passed the current value of the state for chats rather than the initial state.
I thought that because the function containing this code is in the function that declares the chat state, whenever that state changed the whole function would be rendered with the new value of chats making it available to its nested functions. This isn't the case here and I don't understand why.
The solution, to only trust the values of the state that is handed to me during the setState (setChats) call, works fine and I'll go with it, but I'd love to know what is wrong with reading the state directly.

Function A depends on previous function to update the state, but Function A still tries to render before the update

An Example I have linked below, that shows the problem I have.
My Problem
I have these two functions
const updatedDoc = checkForHeadings(stoneCtx, documentCtx); // returns object
documentCtx.setUserDocument(updatedDoc); // uses object to update state
and
convertUserDocument(stoneCtx, documentCtx.userDocument);
// uses State for further usage
The Problem I have is, that convertUserDocument runs with an empty state and throws an error and then runs again with the updated state. Since it already throws an error, I cannot continue to work with it.
I have tried several different approaches.
What I tried
In the beginning my code looked like this
checkForHeadings(stoneCtx, documentCtx);
// updated the state witch each new key:value inside the function
convertUserDocument(stoneCtx, documentCtx.userDocument);
// then this function was run; Error
Then I tried the version I had above, to first put everything into an object and update the state only once.
HavingconvertUserDocument be a callback inside of checkForHeadings, but that ran it that many times a matching key was found.
My current try was to put the both functions in seperate useEffects, one for inital render and one for the next render.
const isFirstRender = useRef(true);
let init = 0;
useEffect(() => {
init++;
console.log('Initial Render Number ' + init);
console.log(documentCtx);
const updatedDoc = checkForHeadings(stoneCtx.stoneContext, documentCtx);
documentCtx.setUserDocument(updatedDoc);
console.log(updatedDoc);
console.log(documentCtx);
isFirstRender.current = false; // toggle flag after first render/mounting
console.log('Initial End Render Number ' + init);
}, []);
let update = 0;
useEffect(() => {
update++;
console.log('Update Render Number ' + update);
if (!isFirstRender.current) {
console.log('First Render has happened.');
convertUserDocument(stoneCtx.stoneContext, documentCtx.userDocument);
}
console.log('Update End Render Number ' + update);
}, [documentCtx]);
The interesting part with this was to see the difference between Codesandbox and my local development.
On Codesandbox Intial Render was called twice, but each time the counter didn't go up, it stayed at 1. On the other hand, on my local dev server, Initial Render was called only once.
On both version the second useEffect was called twice, but here also the counter didn't go up to 2, and stayed at 1.
Codesandbox:
Local Dev Server:
Short example of that:
let counter = 0;
useEffect(()=> {
counter++;
// this should only run once, but it does twice in the sandbox.
// but the counter is not going up to 2, but stays at 1
},[])
The same happens with the second useEffect, but on the second I get different results, but the counter stays at 1.
I was told this is due to a Stale Cloruse, but doesn't explain why the important bits don't work properly.
I got inspiration from here, to skip the initial render: https://stackoverflow.com/a/61612292/14103981
Code
Here is the Sandbox with the Problem displayed: https://codesandbox.io/s/nameless-wood-34ni5?file=/src/TextEditor.js
I have also create it on Stackblitz: https://react-v6wzqv.stackblitz.io
The error happens in this function:
function orderDocument(structure, doc, ordered) {
structure.forEach((el) => {
console.log(el.id);
console.log(doc);
// ordered.push(doc[el.id].headingHtml);
// if (el.children?.length) {
// orderDocument(el.children, doc, ordered);
// }
});
return ordered;
}
The commented out code throws the error. I am console.loggin el.id and doc, and in the console you can see, that doc is empty and thus cannot find doc[el.id].
Someone gave me this simple example to my problem, which sums it up pretty good.
useEffect(() => {
documentCtx.setUserDocument('ANYTHING');
console.log(documentCtx.userDocument);
});
The Console:
{}
ANYTHING
You can view it here: https://stackblitz.com/edit/react-f1hwky?file=src%2FTextEditor.js
I have come to a solution to my problem.
const isFirstRender = useRef(true);
useEffect(() => {
const updatedDoc = checkForHeadings(stoneCtx.stoneContext, documentCtx);
documentCtx.setUserDocument(updatedDoc);
}, []);
useEffect(() => {
if (!isFirstRender.current) {
convertUserDocument(stoneCtx.stoneContext, documentCtx.userDocument);
} else {
isFirstRender.current = false;
}
}, [documentCtx]);
Moving isFirstRender.current = false; to an else statement actually gives me the proper results I want.
Is this the best way of achieving it, or are there better ways?

React - Async background API calls

Our team is working on a webapp / game where we want to make an API call every 8 seconds if a condition is true.
This process is triggered by a button which starts the populate method
async autoLoad() {
const timer = await 8;
console.log('debug consolelog')
const result = await this.UpdateProfile();
}
togglePopulate() {
const status = this.state.autoLoad;
if (status === true) {
this.setState({ autoLoad: false });
} else {
this.setState({ autoLoad: true });
this.autoLoad();
}
}
Our understanding was that this would run the 'UpdateProfile()' function in the background every 8 seconds. The result, however, is that our entire webpage locks up and the UpdateProfile (or the debug console.log) is not run.
Does anyone have an idea what we are doing wrong? (Or if what we are attempting to do is even possible?)
No offense, but I think you may be misunderstanding how async await works if you are trying to set the timer via, const timer = await 8. You may want to read up on some articles that describe exactly what Async Await returns to you.
However, setting a function to be called on a timer is actually kind of confusing with React. I feel like this is more of the problem that you are having. I hope this example helps you.
class Example extends React.Component {
constructor(props) {
super(props)
this.state = {
interval: null
}
this.enableAutoload = this.enableAutoload.bind(this)
this.disableAutoload = this.disableAutoload.bind(this)
}
enableAutoload() {
const setTimer = window.setInterval(() => {
this.iRunEveryEightSeconds()
}, 8000)
this.setState({ interval: setTimer })
}
disableAutoload() {
console.log('Clearing the timer from state...')
const clearTimer = window.clearInterval(this.state.interval)
this.setState({ interval: clearTimer })
}
iRunEveryEightSeconds() {
console.log('Hello There.')
}
render() {
return (
<div className="example">
Example API Call Every 8 Seconds
<button onClick={this.enableAutoload} className="enable">
Enable Autoload
</button>
<button onClick={this.disableAutoload} className="disable">
Disable Autoload
</button>
</div>
)
}
}
ReactDOM.render (
<Example />,
document.querySelector('#app')
)
I know that you need to run this API call when a specific condition is met. You can use this example to see how to set the timer into state when the condition is true, and clear the interval in state when it evaluates to false. Make sure to check out the console in the Codepen example below after you click the enable button, and when you click the disable button. "Hello There" will stop being printed every 8 seconds after you click the disable button.
The included Codepen example, will help you out further. Any questions, please ask!

Categories

Resources