infinite requestAnimationFrame Loop in next.js - javascript

I am trying to recreate this app in next.js https://codelabs.developers.google.com/tensorflowjs-transfer-learning-teachable-machine#0
To start the data collection, I added a onMouseDown MouseEvent on the button which, triggers this code:
const handleGatherDataForClass: MouseEventHandler = (e) => {
let classNumber = parseInt(e.target.getAttribute('data-1hot'));
console.log('inside EventHandler:', { classNumber });
let state = gatherDataState === STOP_DATA_GATHER ? classNumber : STOP_DATA_GATHER;
console.log({ state });
setGatherDataState(state);
};
Where
classNumber = positive integer
STOP_DATA_GATHER = -1
gatherDataState = -1 (default)
Here is the original code snippet from the working app: https://codelabs.developers.google.com/tensorflowjs-transfer-learning-teachable-machine#11
After the state of the gatherDataState variable changes, the useEffect hook should run the dataGatherLoop function, which takes frames from the video stream and converts it into tensors:
useEffect(() => {
dataGatherLoop();
}, [gatherDataState]);
function dataGatherLoop() {
console.log('inside Loop: ', {
gatherDataState
});
if (videoPlaying && gatherDataState !== STOP_DATA_GATHER) {
let imageFeatures = tf.tidy(function() {
let videoFrameAsTensor = tf.browser.fromPixels(VIDEO);
let resizedTensorFrame = tf.image.resizeBilinear(
videoFrameAsTensor, [MOBILE_NET_INPUT_HEIGHT, MOBILE_NET_INPUT_WIDTH],
true
);
let normalizedTensorFrame = resizedTensorFrame.div(255);
return mobilenet.predict(normalizedTensorFrame.expandDims()).squeeze();
});
imageFeatures.print();
setTrainData((prev) => ({
trainX: [...prev.trainX, imageFeatures],
trainY: [...prev.trainY, gatherDataState],
}));
// Intialize array index element if currently undefined.
let newCount = [...examplesCount];
if (examplesCount[gatherDataState] === undefined) {
newCount[gatherDataState] = 1;
setExamplesCount(newCount);
} else {
newCount[gatherDataState]++;
setExamplesCount(newCount);
}
window.requestAnimationFrame(dataGatherLoop);
}
}
This loop runs, as long as the gatherDataState variable is a positive integer (not equal to -1)
After the mouse button is released, an onMouseUp event is triggered which runs the same handleGatherDataForClass function as the onMouseDown event. This should change the state back to -1 and therefore stop the Loop.
Problem:
Even though the state is changing to -1 after the onMouseUp event is triggered, the gatherDataState ends up being a positive integer every time.. Therefore the loop is not stopping. (there is NO setGatherDataState function anywhere else in the code)
I tried:
writing the gatherDataLoop function inside the handleGatherDataForClass event handler and passing the gatherDataState variable as an argument
using a global variable for gatherDataState instead of a react state to save the current gatherDataState
canceling the requestAnimationFrame loop with the cancelAnimationFrame function (saving the id globally and as state)

I am not exactly sure why, but
using useRef to store the values being used inside the requestAnimationFrame loop, as well as the id for canceling the loop and
taking the loop out of the useEffect Hook and writing it inside the ClickEventhandler
worked for me.
Here is the code:
const handleGatherDataForClass: MouseEventHandler = (e) => {
let classNumber = parseInt(e.target.getAttribute('data-1hot'));
gatherDataStateRef.current = classNumber;
isCollectingRef.current = !isCollectingRef.current;
if (isCollectingRef.current) {
collectRequestRef.current = requestAnimationFrame(dataGatherLoop);
} else {
cancelAnimationFrame(collectRequestRef.current);
}
};
function dataGatherLoop() {
console.log('inside Loop: ', gatherDataStateRef.current);
let imageFeatures = tf.tidy(function () {
let videoFrameAsTensor = tf.browser.fromPixels(VIDEO);
let resizedTensorFrame = tf.image.resizeBilinear(
videoFrameAsTensor,
[MOBILE_NET_INPUT_HEIGHT, MOBILE_NET_INPUT_WIDTH],
true
);
let normalizedTensorFrame = resizedTensorFrame.div(255);
return mobilenet.predict(normalizedTensorFrame.expandDims()).squeeze();
});
imageFeatures.print();
setTrainData((prev) => ({
trainX: [...prev.trainX, imageFeatures],
trainY: [...prev.trainY, gatherDataStateRef.current],
}));
if (examplesCountRef.current[gatherDataStateRef.current] === undefined) {
examplesCountRef.current[gatherDataStateRef.current] = 1;
} else {
examplesCountRef.current[gatherDataStateRef.current] += 1;
}
collectRequestRef.current = requestAnimationFrame(dataGatherLoop);
}

Related

addEventListener returns undefined in Javascript

I want set the addEventListner value to int value,
const stringItem = window.addEventListener("click",(e) => {
const itemTarget = e.target;
const itemParent = itemTarget.parentElement.id;
const strItem = parseInt(itemParent.slice(5));
console.log(strItem);
return strItem;
}, false);
let currentItem = stringItem;
console.log(currentItem);
stringItem return undefined, but I want the strItem to be returned
I want to access the strItem value outside the addEventListener.
How do I do that?
The addEventListener returns undefined as a function (see link). You are passing a function to the addEventListener which gets called whenever you click on the window. The return value of that function will be lost. To be able to use that value outside of the function you will have to do something like this:
let stringItem;
window.addEventListener("click",(e) => {
const itemTarget = e.target;
const itemParent = itemTarget.parentElement.id;
const strItem = parseInt(itemParent.slice(5));
console.log(strItem);
stringItem = strItem;
return strItem;
}, false);
The last two line of your code wouldn't work as they're executed as soon as the eventListener gets added. The currentItem will always be undefined. I would advise you to read more on using callback function in javascript.
The return value of the callback function is discarded. It doesn't make sense to return anything. window.addEventListener doesn't return anything. It doesn't make sense to store the result in a variable stringItem.
You can create a variable outside of the function and store the value in it:
let value = 0;
document.querySelector('#button1').addEventListener("click",(e) => {
++value;
}, false);
document.querySelector('#button2').addEventListener("click",(e) => {
value = 0;
}, false);
document.querySelector('#button3').addEventListener("click",(e) => {
console.log(value);
}, false);
<button id="button1">Increment</button>
<button id="button2">Reset</button>
<button id="button3">Show</button>

how to use variable immediately after it's defined - js

How, sorry for this weird title, i didn't know how to put this... Here my explanation:
I have a function where I define variable (I'm looping over an array and if condition is matched, I define let). It works everytime item.dataset.category changes, so after every scroll.
After the loop, I call another function, where I use this variable as an argument. In the second function I use it to check if another condition is matched:
//first function
const getDataset = (e) => {
let dataset;
const cat = Array.from(categories.children);
cat.forEach((item) => {
if (item.className.includes('active')) {
dataset = item.dataset.category;
}
});
changeNavActive(dataset);
};
//second function
const changeNavActive = (dataset) => {
const navItems = Array.from(navList.children);
navItems.forEach((item) => {
item.classList.remove('active');
if (item.dataset.category === dataset) {
item.classList.add('active');
}
});
};
It's not working and I think I understand why - callign of second function is at the same time as declaring variable, so I geting this let in the next call. The result is that second function works with delay of one scroll.
This is a function which calls getDataset():
const scrollRows = (e) => {
if (window.scrollY > slider.clientHeight) {
e.deltaY > 0 ? move++ : move--;
getDataset();
if (move > categories.children.length - 1)
move = categories.children.length - 1;
}
}
How to fix this?

Next Js Router beforePopState , access state of component in handler

I am using Next js Router.beforePopState event and i want to access value of useState variable which i created in react component in which this handler is defined, sample code below , but whenever i access the state it is empty or set to default value for example in below code goaway state value i always get in handler as empty string "" even though i have set in code a different value, is there a way i can get the state value in handler?
const [goAway, setGoAway] = useState("");
const browserTabcloseHandler = e => {
e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
e.returnValue = "";
};
useEffect(() => {
if (window) {
Router.beforePopState(() => {
//if (componentShouldBeSavedAsDraft(componentData)) {
const result = window.confirm("are you sure you want to leave?");
if (!result) {
window.history.pushState("/", "");
Router.push("/marketplace/upload-component");
}
console.log(goAway); // this value is always "" even though set differently in code.
return result;
});
window.onbeforeunload = browserTabcloseHandler;
}
//Router.events.on("routeChangeStart", handleRouteChange);
return () => {
if (window) {
window.onbeforeunload = null;
}
Router.beforePopState(() => {
return true;
});
};
}, []);
i was able to find solution for same what i was missing is adding property in useEffect below code fixed the issue-
const [goAway, setGoAway] = useState("");
const browserTabcloseHandler = e => {
e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown
// Chrome requires returnValue to be set
e.returnValue = "";
};
useEffect(() => {
if (window) {
Router.beforePopState(() => {
//if (componentShouldBeSavedAsDraft(componentData)) {
const result = window.confirm("are you sure you want to leave?");
if (!result) {
window.history.pushState("/", "");
Router.push("/marketplace/upload-component");
}
console.log(goAway); // this value is always "" even though set differently in code.
return result;
});
window.onbeforeunload = browserTabcloseHandler;
}
//Router.events.on("routeChangeStart", handleRouteChange);
return () => {
if (window) {
window.onbeforeunload = null;
}
Router.beforePopState(() => {
return true;
});
};
}, [goAway]); // this fixed the issue

When boolean turns false, check 5 seconds if it will turn back to true, else do some action

I have in NodeJS a variable that updates every second. I want to monitor it to see if it turns below a certain threshold (e.g. 1000).
If it does go below the threshold, it should wait 5 seconds and monitor if the variable goes back above again. If not, it should return a function. If it does go above, it can stop the times and start monitoring again.
Can't get any code to work.
Not sure if the code below is even in the right direction..!
var waitedSoFar = 0;
var imageDisplayed = CheckIfImageIsDisplayed(); //this function is where you check the condition
while(waitedSoFar < 5000)
{
imageDisplayed = CheckIfImageIsDisplayed();
if(imageDisplayed)
{
//success here
break;
}
waitedSoFar += 100;
Thread.Sleep(100);
}
if(!imageDisplayed)
{
//failed, do something here about that.
}
You could have a function that observe and wrap this variable that change so often. Something as simple as
function observeValue(initialValue) {
let value = initialValue
let clearEffects = []
let subscribers = []
return {
value: () => value,
change(val) {
clearEffects.forEach(oldEffect => oldEffect && oldEffect())
value = val
clearEffects = subscribers.map(subscriber => subscriber(value))
},
subscribe(listener) {
subscribers.push(listener)
}
}
}
is generic enough. The function returns three methods:
one to get the current value
one to change the value
on to subscribe on every value change
I would suggest to leverage the last one to monitor and trigger any side effects.
You can use it like this:
const monitor = observeValue(true)
monitor.subscribe((value) => {
if (value !== false) {
return
}
const timeout = setTimeout(() => {
console.log('value was false for five seconds')
}, 5000)
return () => {
clearTimeout(timeout)
}
})
and somewhere else you can change the value with
monitor.change(false)

Cross-Listen to multiple events and trigger callback

I have a list of event names which I need to listen to stored in an array, like so:
var events = ['A', 'B'];
Now, I'm unsure which event will be triggered first and it could be very inconsistent (depends on the HTTP requests that they await) so I can never safely listen to only one of them. So, I need to somehow "cross-listen" to all of them in order to trigger my original callback.
So my idea was to do the following:
Create a listener for A, which creates a listener for B. B's listener triggers the callback.
Create a listener for B, which creates a listener for A. A's listener triggers the callback.
So this would result in 4 listners, which would look something like this (pseudo-code):
var callback = function() { console.log('The callback'); };
Event.on('A', function () {
Event.on('B', callback);
});
Event.on('B', function () {
Event.on('A', callback);
});
So I believe this would solve my issue (there's probably another problem that I'm not seeing here though).
The issue is, I can make this work when there are only 2 events I need to listen to. But what about when I have 3-4 events I want to "cross-listen" to? Lets say we have ['A', 'B', 'C', 'D']. This would obviously require looping through the events. This part is what's confusing me and I'm not sure how to proceed. This would need to register a nice combination of events.
This needs to be done in JavaScript.
My imagination and logic is limited in this case.
I was thinking something like this:
var callback = function() { console.log('The callback'); };
var events = {
'click': false,
'mouseover': false,
'mouseout': false
};
for(prop in events) {
$('.evt-button').on(prop, function(evt) {
if(events[evt.type] === false) {
console.log('First ' + evt.type + ' event');
events[evt.type] = true;
checkAll();
}
});
}
function checkAll() {
var anyFalse = false;
for(prop in events) {
if(events.hasOwnProperty(prop)) {
if(events[prop] === false) {
anyFalse = true;
break;
}
}
}
if(!anyFalse) {
callback();
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button class="evt-button">The button</button>
There's lots of ways to do what you're asking, but to keep it simple you could have an array of event names, as you already do, and simply remove them as they occur, checking to see if the array is empty each time. Like this...
var events = ['A', 'B'];
var callback = function() { console.log('The callback'); };
var eventOccured = function(eventName) {
// if the event name is in the array, remove it...
var idx = events.indexOf(eventName);
if (idx !== -1) {
events.splice(events.indexOf(idx), 1);
}
// if the event array is empty then we've handled everything...
calback();
};
Event.on('A', function () {
// do whatever you need in the event handler
eventOccured("A");
});
Event.on('B', function () {
// do whatever you need in the event handler
eventOccured("B");
});
A bit late, but you can make a function that wraps your callback and reuse it to attach multiple eventlisteners and count until all have occurred. This way you can also add events to cancel out others, say keyup cancels keydown.
const K = a => _b => a
const makeEventTrigger = (fn) => {
let lastTarget,
required = 0,
active = 0;
return (enable, qualifier = K(true)) => {
required = enable ? required + 1 : required;
return (event) => {
if(!qualifier(event)) {
return
}
const isLastTarget =
lastTarget && lastTarget.isEqualNode(event.currentTarget);
if (!enable) {
lastTarget = isLastTarget ? null : lastTarget;
active = Math.max(0, active - 1);
return;
}
if (!isLastTarget) {
lastTarget = event.currentTarget;
active = Math.min(required, active + 1);
return active === required && fn();
}
};
};
};
let changeTitleCol = () =>
(document.querySelector("h1").style.color =
"#" + Math.random().toString(16).slice(-6));
let addTriggerForColorChange = makeEventTrigger(changeTitleCol);
let isSpace = e => e.key === " " || e.code === "Space"
document
.querySelector("button")
.addEventListener("mousedown", addTriggerForColorChange(true));
document
.querySelector("button")
.addEventListener("mouseup", addTriggerForColorChange(false));
document.addEventListener("keydown", addTriggerForColorChange(true, isSpace));
document.addEventListener("keyup", addTriggerForColorChange(false));
<h1>Multiple Event sources!</h1>
<div>
<button id="trigger-A">klik me and press space</button>
</div>

Categories

Resources