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

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?

Related

infinite requestAnimationFrame Loop in next.js

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);
}

Function doesn't wait react hooks

I'm trying to add items in shopping cart. It works, but after adding items when I want to calculate number of items to show on the shopping cart. Second function (calculate()) doesn't wait items hooks. Because of that, it shows the correct count after adding second item.
Below code is my functions. As you can see, in the end of first function I'm calling the calculate() function to keep it continue.
const [testArray, setTestArray] = useState([]);
const [total, setTotal] = useState(0);
const [cartCount, setCartCount] = useState(0);
function addToTest(product, quantity = 1) {
const ProductExist = testArray.find((item) => item.id === product.id);
if (ProductExist) {
setTestArray(
testArray.map((item) => {
if (item.id === product.id) {
return { ...ProductExist, quantity: ProductExist.quantity + 1 };
} else {
return item;
}
})
);
} else {
product.quantity = 1;
setTestArray([...testArray, product]);
}
calculate();
}
function calculate() {
let resultCount = 0;
testArray.map((item) => {
console.log("map func works!");
setCartCount(cartCount + item.quantity);
});
}
Here is my codesandbox project, I kept it very simple to not bother you.
https://codesandbox.io/s/react-template-forked-u95qt?file=/src/App.js
The possible problem occurs due to synchronise functions. Because of that, when I try to async/await, I'm getting error about parameters, because the first function has parameter.
This is my first try for async/await:
async function calculate(product) {
await addToTest(product);
let resultCount = 0;
testArray.map((item) => {
console.log("map func works!");
setCartCount(cartCount + item.quantity);
});
}
As other solution I tried to use useEffect by taking the reference setArray hooks. However, in this case, the count number increases exponentially like 1,3,9...
useEffect(()=>{
let resultCount = 0;
testArray.map((item) => {
console.log("map func works!");
setCartCount(cartCount + item.quantity);
});
},[testArray])
I wonder where is the problem? Because when I use the the upper code in Angular/Typescript, it works properly. I think this happens due to react hooks, but I couldn't understand the problem.
Your thought to use a useEffect is a good one, but instead of calling setCartCount on every iteration you should instead sum all the item.quantity counts first and then call setCartCount once after your loop.
Note: don't use map() if you're going to ignore the array it returns, use a for loop, for...of as in the example below, or forEach() instead.
useEffect(() => {
let resultCount = 0;
for (const item of testArray) {
resultCount += item.quantity;
}
setCartCount(resultCount);
}, [testArray]);
or with reduce() (here destructuring and renaming the quantity property of each passed item)
useEffect(() => {
const resultCount = testArray.reduce((a, { quantity: q }) => a + q, 0);
setCartCount(resultCount);
}, [testArray]);
Every call to setCartCount will only update cartCount on the next render, so you are (effectively) only calling it with the final item in the array. You should instead use reduce on the array to get the value you want, and then set it.
See https://codesandbox.io/s/react-template-forked-u95qt?file=/src/App.js

passing an array to a function but logs as undefined

I have 2 event listeners and they both push the element that is clicked into 2 separate arrays. These console.log in the event listener functions as expected, however when I pass them into a move function they both return undefined.
I've tried it with just passing the element that is selected into the function as well with the same result.
document.querySelectorAll('img').forEach(function(el) {
el.addEventListener('click' , function(params) {
el.classList.add('selected')
selectedPieces.push(el)
console.log(selectedPieces)
moveBy(selectedPieces)
})
})
document.querySelectorAll('.square').forEach(function(el) {
el.addEventListener('click', function() {
el.classList.add('target')
targetPieces.push(el)
console.log(targetPieces)
moveBy(targetPieces)
})
})
function moveBy(selected,target) {
console.log(selected)
console.log(target)
}
So I did create a work around in this. It involved sending the addEventListeners to their own functions, then adding them to an array, then sending that array to it's own function which then allowed me to move things around.
Only thing wrong with this version is that it only works one time, so I'm currently working through what is causing it to only fire once.
document.querySelectorAll('img').forEach(function(el) {
el.addEventListener('click' , addselected)
})
document.querySelectorAll('.square').forEach(function(el) {
el.addEventListener('click', addtarget)
})
function addselected(selected){
console.log('---addselected---')
var select = selected.path[0]
moveList.push(select)
}
function addtarget(target){
console.log('---addtarget---')
if (target.path.length === 7){
var tar = target.path[1]
}else{
var tar = target.path[0]
}
moveList.push(tar)
moveBy(...moveList)
}
function moveBy(...moveList) {
console.log('---moveBy---')
let moveTarg = moveList[2]
let moveSel = moveList[0]
if (typeof moveTarg === 'undefined') {
console.log('not working')
}else{
moveTarg.appendChild(moveSel)
moveList = []
}
console.log(moveList)
}
well
function moveBy(selected,target) {
console.log(selected)
console.log(target)
}
has two paramaters.
moveBy(targetPieces)
but you pass only one parameter in.

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)

understanding the return keyword

I have this:
validateForm = () => {
for (let i = 0; i < formInputs.length; i++) {
const inputName = formInputs[i];
if (!this.state.form[inputName].length) {
return false;
}
}
}
which im refactoring in to this:
validateForm2 = () => {
Object.keys(this.state.form).map(input => {
if(!this.state.form[input].length) {
return false
}
return true;
})
}
the first one works, when i fill in my form and the function returns true, if one is empty it returns false.
however i cant seem to quite understand the return keyword to get the same result.
Object.keys says it returns an array but even if I say return Object.keys... or else {return true} I don't seem to get the same result. what am I misunderstanding about return?
You could use Array#every, which uses the return value for a short circuit and for returning the check of all truthy items.
validateForm2 = () =>
Object.keys(this.state.form).every(input => this.state.form[input].length);
Array#map utilizes the return value as new item for each item of the array for a new array, which is dicarded in the given example.
In the first example you have only one (arrow) function which returns either false or undefined.
In the second example you have outer (arrow) function that never returns anything - undefined to the calling code, and the second function that you pass as a parameter to Array.map method. return statements inside the parameter function are not returning anything from the outer function.
validateForm2 = () => {
var emptyItems = Object.keys(this.state.form).filter(input => {
return !this.state.form[input].length;
});
return emptyItems.length == 0;
}
You could modify your function to do what you want it to do.
validateForm2 = () => {
return Object.keys(this.state.form).every(input => {
return this.state.form[input].length;
})
}
You are checking that every property has a length (true). If one of them doesn't, your function returns false.
I think you can avoid using .map in favor of .every() which iterates over every single element and checks whether it has a length greater than zero.
const validateForm = (form) => Object.values(form).every((field) => field.length);
let semiEmptyForm = {
firstField : "",
secondfield : "notEmpty"
};
let nonEmptyForm = {
firstField : "notEmpty",
secondfield : "notEmpty"
};
console.log(validateForm(semiEmptyForm))
console.log(validateForm(nonEmptyForm))

Categories

Resources