JS/React - Components lose touch functionality when passed through useEffect/setInterval? - javascript

I am making a simple space shooter style game. The bad guys will be sent to the screen in intervals (similar to tetris).
The bad guys have all their touch functionality working when I pass them in an array manually but when I try to send them to the screen in intervals they lose all touch functionality.
I first tried a useEffect hook with setInterval. The interval would filter the array of bad guys. That was where the problem first popped up. Then, I tried a custom useInterval hook and got the same result.
Lastly - rather than set the state of the array of onscreen villains, I thought I would keep the villains out of the setInterval and just increment indexes, and return a slice of them. This did allow the first villain to gain touch ability but ... I was again frustrated by failure.
Here is the offending code -
aliensArray is the array of alien components for the user to battle.
function runTheInterval(){
const difference = slice.right - slice.left;
if(slice.right < aliensArray.length-1){
slice.right += 1;
}
if (slice.left < slice.right && difference > 3 && difference < 5){
slice.left += 1;
}else if (slice.left < slice.right && slice.right === aliensArray.length-1){
slice.left += 1;
}
setSlice(() => {
return { right: slice.right, left: slice.left }
})
}
const interValue = useInterval(runTheInterval, 4000);
----Other code----
const liveOnScreen = aliensArray.slice(slice.left, slice.right);
<>
{liveOnScreen}</>
So the only thing to pass through the interval is the indices of the villains array to be filtered. What am I doing wrong??
Thanks
Here is the custom useInterval hook that I used.
const intervalRef = useRef(null);
const savedCallback = useRef(callback);
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => savedCallback.current();
if (typeof delay === 'number') {
intervalRef.current = window.setInterval(tick, delay);
return () => window.clearInterval(intervalRef.current);
}
}, [delay]);
return intervalRef;
}```

Related

Infinite for loop in useEffect when i want to check if a property of window object is here

I want to check on load if the object window has the property VL.refCode.
I use viral loops for my website and viral loop put in window.VL object the property refCode when a user is registered.
I put a for loop in a useEffect to check if this property exists or not, but unfortunately it becomes an infinite loop.
useEffect(() => {
if(window.VL){
if(window.VL.refCode){
setCryptoButtonMessage(t('common:cryptoButton_already_register'))
console.log('useEffect :user already register')
}
else{
console.log('useEffect : window.vl.refcode not exist')
}
}
else {
setCryptoButtonMessage(t('common:cryptoButton_title_waitlist'))
}
// t is for translation with i18n
},[t]);
This solution doesn't work because viral loop create window.VL object 1 to 2sec max after the first render.
If I put a setTimeout it's not a valid solution for users with mobile device / slow 3g / without fiber
So i use this solution
useEffect(() => {
let animation;
const check = () => {
console.log('je suis dans check');
console.log('je suis dans for');
if ((Object.prototype.hasOwnProperty.call(window.VL, 'refCode'))) {
console.log('je trouve refCode ');
setCryptoButtonMessage(t('common:cryptoButton_already_register'))
cancelAnimationFrame(animation);
return;
}
animation = requestAnimationFrame(check);
};
animation = requestAnimationFrame(check);
},[t]);
But this solution will never stop until window.VL.refCode exist ... not really best solution for website performance ...
I try to put a " simulate timer " but it becomes an infinite loop ...
useEffect(() => {
for (let i = 1; i < 10000; i += 1) {
let animation;
const check = () => {
if ((Object.prototype.hasOwnProperty.call(window.VL, 'refCode'))) {
setCryptoButtonMessage(t('common:cryptoButton_already_register'))
cancelAnimationFrame(animation);
return;
}
animation = requestAnimationFrame(check);
};
animation = requestAnimationFrame(check);
}
},[t]);
I don't know of any way of being automatically told when an object gets created on the window - so as far as I know the only way to do this is with a poll. If it was me, I'd abstract the logic for polling for the ref code into a new hook:
import { useEffect, useRef, useState } from 'react';
const useRefCode = (pollTimeMillis = 1000) => {
const timeoutRef = useRef(undefined);
const [refCode, setRefCode] = useState(window.VL?.refCode);
const cancelTimeout = () => {
if (!timeoutRef.current) return;
clearTimeout(timeoutRef.current);
timeoutRef.current = undefined;
};
useEffect(() => {
if (refCode) return;
setTimeout(() =>
setRefCode(window.VL?.refCode || undefined),
pollTimeMillis
);
return cancelTimeout;
}, [refCode]);
return refCode;
}
Then your other component that wants to change the message based on the ref code becomes very simple:
const OtherComponent = () => {
const refCode = useRefCode();
const translationKey = refCode
? 'common:cryptoButton_already_register'
: 'common:cryptoButton_title_waitlist';
return (
<div>{t(translationKey)}</div>
);
}

Use setInterval without increasing memory consuming

I use setInterval in my code to make my slider work. It should change image each 5 second . But I see that when I use it , it consume new memory all the time . How can I use setInterval without consuming new memory ?
const Slider = ({ children, sliderIndex, setSlideIndex }) => {
const classes = useStyles()
useInterval(() => {
setSlideIndex((sliderIndex + 1) % children.length)
}, 5000)
const animateFunc = useCallback(
(child, indx) => {
return (
(indx === sliderIndex && (
<div className={classes.root} key={indx}>
{children[sliderIndex].image}
</div>
)) ||
null
)
},
[children, classes.root, sliderIndex]
)
return children.map(animateFunc)
}
i use #use-it/interval hook for it https://www.npmjs.com/package/#use-it/interval
It seems unlikely to me that the interval is actually causing your issue here.
Below is a snippit that will run for two minutes - printing a new line to the console each second. Take a look at the memory usage over time in this page - it won't grow. You can even add a heavy for/while function into it if you like. I think your issue is elsewhere in your code and there are some good comments above to follow up.
let n = 1;
let myInterval = setInterval(function(){ console.log("hello number:" + n++); shouldIStop(120) }, 1000);
function shouldIStop(maxCount){
if (n >= maxCount){
clearInterval(myInterval);
}
}

What am I doing bad with localStorage?

I am doing a time-to-click score table but first I have to localstorage each time I get in a game but I dont know how I have been trying everything but it still not working, I need to finish this fast, and I need help... otherwise I would try everyday to resolve this alone because I know that's the way to learn.. When I press the finished button It says that times.push() is not a function.
let times = Array.from(
{ length: 3 }
)
let interval2;
// Timer CountUp
const timerCountUp = () => {
let times = 0;
let current = times;
interval2 = setInterval(() => {
times = current++
saveTimes(times)
return times
},1000);
}
// Saves the times to localStorage
const saveTimes = (times) => {
localStorage.setItem('times', JSON.stringify(times))
}
// Read existing notes from localStorage
const getSavedNotes = () => {
const timesJSON = localStorage.getItem('times')
try {
return timesJSON ? JSON.parse(timesJSON) : []
} catch (e) {
return []
}
}
//Button which starts the countUp
start.addEventListener('click', () => {
timerCountUp();
})
// Button which stops the countUp
document.querySelector('#start_button').addEventListener('click', (e) => {
console.log('click');
times = getSavedNotes()
times.push({
score: interval2
})
if (interval) {
clearInterval(interval);
clearInterval(interval2);
}
})
You probably need to change the line times = JSON.parse(localStorage.times || []) to times = JSON.parse(localStorage.times) || [] this makes sure that if the parsing fails it will still be an array.
Even better is wrap them with try-catch just in case if your JSON string is corrupted and check if the time is an array in the finally if not set it to empty array.

How to make a localStorage not reset when the app renders?

I have created a localStorage that adds +1 every time the user visits my app. The problem is that every time I refresh the page, the value goes back to 0.
This is my code:
localStorage.setItem('timesVisited', 0);
useEffect(() => {
if (localStorage.getItem('timesVisited') < 5) {
let timesVisited = parseInt(localStorage.getItem('timesVisited'));
localStorage.setItem('timesVisited', ++timesVisited);
}
},[])
The idea is to make the localStorage sum +1 until it reaches 5 but it keeps on 0. What I'm doing wrong?
This line will always run and resets any previous value
localStorage.setItem('timesVisited', 0);
turn it into
if(!localStorage.getItem('timesVisited')){
localStorage.setItem('timesVisited', 0)
}
Try something like this, if localstorage not set then set 0 otherwise increment
useEffect(() => {
if (!localStorage.getItem('timesVisited')) {
localStorage.setItem('timesVisited', 0);
} else if (localStorage.getItem('timesVisited') < 5) {
let timesVisited = parseInt(localStorage.getItem('timesVisited'));
localStorage.setItem('timesVisited', ++timesVisited);
}
}, [])
If the lines you provided in the component's body, then not only will it be called every time a user visits your app, but every time the component renders.
For the behaviour you're looking for, you should load the value, and set it to 0 if it doesn't exist. You should do this outside of your component, too.
Also, note that localStorage only stores strings, so you'll want to parse what you fetch from it.
const storedValue = localStorage.getItem('timesVisited')
// localStorage only stores string
const visitsCount = storedValue ? parseInt(storedValue) : 0
function myComponent = () => {
useEffect(() => {
if(visitsCount >= 5){
return
}
localStorage.setItem('timesVisited', ++visitsCount);
}, [])
}

Limit number of records in firebase

Every minute I have a script that push a new record in my firebase database.
What i want is delete the last records when length of the list reach a fixed value.
I have been through the doc and other post and the thing I have found so far is something like that :
// Max number of lines of the chat history.
const MAX_ARDUINO = 10;
exports.arduinoResponseLength = functions.database.ref('/arduinoResponse/{res}').onWrite(event => {
const parentRef = event.data.ref.parent;
return parentRef.once('value').then(snapshot => {
if (snapshot.numChildren() >= MAX_ARDUINO) {
let childCount = 0;
let updates = {};
snapshot.forEach(function(child) {
if (++childCount <= snapshot.numChildren() - MAX_ARDUINO) {
updates[child.key] = null;
}
});
// Update the parent. This effectively removes the extra children.
return parentRef.update(updates);
}
});
});
The problem is : onWrite seems to download all the related data every time it is triggered.
This is a pretty good process when the list is not so long. But I have like 4000 records, and every month it seems that I screw up my firebase download quota with that.
Does anyone would know how to handle this kind of situation ?
Ok so at the end I came with 3 functions. One update the number of arduino records, one totally recount it if the counter is missing. The last one use the counter to make a query using the limitToFirst filter so it retrieve only the relevant data to remove.
It is actually a combination of those two example provided by Firebase :
https://github.com/firebase/functions-samples/tree/master/limit-children
https://github.com/firebase/functions-samples/tree/master/child-count
Here is my final result
const MAX_ARDUINO = 1500;
exports.deleteOldArduino = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
return countRef.once('value').then(snapCount => {
return collectionRef.limitToFirst(snapCount.val() - MAX_ARDUINO).transaction(snapshot => {
snapshot = null;
return snapshot;
})
});
});
exports.trackArduinoLength = functions.database.ref('/arduinoResponse/{resId}/timestamp').onWrite(event => {
const collectionRef = event.data.ref.parent.parent;
const countRef = collectionRef.parent.child('arduinoResCount');
// Return the promise from countRef.transaction() so our function
// waits for this async event to complete before it exits.
return countRef.transaction(current => {
if (event.data.exists() && !event.data.previous.exists()) {
return (current || 0) + 1;
} else if (!event.data.exists() && event.data.previous.exists()) {
return (current || 0) - 1;
}
}).then(() => {
console.log('Counter updated.');
});
});
exports.recountArduino = functions.database.ref('/arduinoResCount').onWrite(event => {
if (!event.data.exists()) {
const counterRef = event.data.ref;
const collectionRef = counterRef.parent.child('arduinoResponse');
// Return the promise from counterRef.set() so our function
// waits for this async event to complete before it exits.
return collectionRef.once('value')
.then(arduinoRes => counterRef.set(arduinoRes.numChildren()));
}
});
I have not tested it yet but soon I will post my result !
I also heard that one day Firebase will add a "size" query, that is definitely missing in my opinion.

Categories

Resources