How to correctly use setInterval for multiple functions being called repeatedly in React Native - react-native

I am building a simple app in React Native that aims to flash different colors on the screen at certain time intervals. My implementation is as follows:
useEffect(() => {
var blinkOnValue;
var blinkOffValue;
function blinkOn() {
const colorAndWord = getRandomColor(colorArray);
setBackground(colorAndWord.color);
}
function blinkOff() {
setBackground('#F3F3F3');
}
if (strobeStart) {
if (on) {
blinkOnValue = setInterval(() => {
blinkOn();
setOn(false);
}, info.length * 1000);
} else {
blinkOffValue = setInterval(() => {
blinkOff();
setOn(true);
}, info.delay * 1000);
}
}
return () => {
on ? clearInterval(blinkOnValue) : clearInterval(blinkOffValue);
};
}, [colorArray, info.delay, info.length, on, strobeStart]);
The blinkOn function sets the background a certain color and the blinkOff function sets the background a default light gray-ish color. These functions should alternate back and forth, blinking on and off at different intervals. For example, if info.length is 2 and info.delay is 0.5, then the color should flash on for 2 seconds and then the screen should be light gray for 0.5 seconds and repeat. However, the duration of both of the blinkOn and blinkOff are happening for the same amount of time, no matter what the two values are. Sometimes it uses the value from info.length, and sometimes it uses the value from info.delay which is also quite strange.
I think it has something to do with components mounting and unmounting correctly but honestly I am quite lost. If anyone has any advice on how to make this code consistently work where it flashes appropriately I would really appreciate it.

Instead of trying to time your events just right, I suggest using a single timer and computing the blink state from the current system time.
var oldState = true;
function blink() {
var ms = new Date().getTime();
var t = ms % (info.delay + info.length);
var state = (t < info.length ? true : false);
if (state == oldState) return;
if (state) {
blinkOn();
}
else
{
blinkOff();
}
oldState = state;
}
Now set a short timer to check the time and update the blink state as needed:
setInterval( () => blink(), 100 );

Related

change setInterval to requestAnimationFrame

How do I change the following code? I did not know how to convert this code. Can anyone help me?
let timer = ref(30);
let interval = setInterval(() => {
if (timer.value === 0) {
clearInterval(interval)
alert('done')
} else {
timer.value--
}
}, 1000)
This should work the same way as your original code. Of course, you don't need to pass an interval since requestAnimationFrame will be timed to the screen refresh rate.
let timer = ref(30);
function mainLoop() {
if (timer.value === 0) {
alert('done')
} else {
timer.value--
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
First the function mainLoop is defined. Then, it is called through requestAnimationFrame at the end of the script to 'kickstart' the loop. The subsequent calls to requestAnimationFrame within the function are there to maintain it.

undefined is not an object (evaluating 'fun.__callAsync') trying to use setTimeout in a component

I'm using React Native and Reanimated and I want an animation to play after 2 seconds.
When a user moves a card, the card should stay at it's new position for 2 seconds, and then move back to it's place.
This is what I have:
const panGesture = useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onActive: event => {
translateX.value = event.translationX;
if (event.translationX <= 0) {
// disabling swiping from right to left
translateX.value = 0;
}
},
onEnd: event => {
const shouldStick = translateX.value >= TRANSLATE_X_THRESHOULD;
if (shouldStick) {
translateX.value = withTiming(120);
runOnJS(moveBack)(translateX);
} else {
translateX.value = withTiming(0);
}
},
});
I tried using setTimeOut to count 2 seconds, and then update translateX but I get this error:
undefined is not an object (evaluating 'fun.__callAsync')
This is moveBack function:
const moveBack = (translateX: Animated.SharedValue<number>) => {
console.log("TRANSLATEX: " + translateX);
setTimeout(() => {
translateX.value = 0;
}, 2000);
}
I don't even see the TRANSLATEX log, so I guess it won't even get there.
I can't really figure out what's the problem or how to word it so I can find a solution.
The solution was way easier than I thought.
I'm using Reanimated 2.2.0 and there is withDelay option to add to the animation and it works great.
This is what I added after translateX.value = withTiming(120); (instead of the runOnJs line):
translateX.value = withDelay(2000, withTiming(0));
So right after setting translateX to 120, it waits 2 seconds, and then setting the value back to 0.
when using runOnJS
const doSomething = () => {
...
} // declare arrow function before runOnJS
runOnJS(doSomething)(arg)
function doSomething(){
...
} // can declare normal function before/after runOnJS coz of hoisting

Is there a cleaner way to organize this recoilJS state?

I've just learned about recoilJS and have been playing around with it a bit and have a question about whether what I've done is considered "correct." My code works, but it feels weird.
I've got the following React function component:
export const TimerPanel: FC = () => {
const gameState = useRecoilValue<GameState>(HeaderAtoms.gameState);
const timerCount = useRecoilValue(HeaderAtoms.timerCount);
const setTimerCounter = useSetRecoilState(HeaderAtoms.timerCounter);
useEffect(() => {
if (gameState === GameState.IN_PROGRESS && timerCount < 1000) {
window.setTimeout(() => {
setTimerCounter(timerCount + 1);
}, 1000);
}
});
return <NumberPanel num={timerCount} />;
};
where the relevant atom and selector are defined as:
export const timerCount = atom<number>({
key: 'Header.timerCount',
default: 0
});
export const timerCounter = selector<number>({
key: 'Header.timerCounter',
get: ({ get }) => {
return get(timerCount);
},
set: ({ get, set }, newCount) => {
if (get(gameState) === GameState.NEW) {
set(timerCount, 0);
} else if (get(gameState) === GameState.IN_PROGRESS) {
set(timerCount, newCount);
}
}
});
Basically, when the game starts, the TimerPanel increments the timer display by 1 every second the game is in progress. If the user resets the game (GameState.NEW), timerCount resets back to zero. If the atom/selector aren't done properly, there's a race condition in that the game state and timer count will reset, but the timer is still going and will still update the TimerPanel one more time. This is why I have the if blocks in my selector's set prop.
Basically, I'm concerned that my timerCounter selector is a glorified filter/pass-thru entity for the timerCount state and am wondering if there's a better way to handle this use case.
If your concern is the race condition, in your code, I don't see how your timer would click. setTimeout will tick once.
Anyway, you have 2 states, game state and timer state. You want to reset timer when game state changes. How about doing it in a game state selector? or move the logic into a custom hook and operate on these 2 states directly.

How to render text only after a Promise has been resolved in React Native?

I am trying to dynamically translate some text to be displayed when a user clicks on the translate button, but I can't get it to save my values outside of the Promise. I haven't worked much with Promises and every example only shows console.log, rather than saving values outside of the Promise. I don't really understand how they work. Here is (most of) the code I am trying to fix:
constructor(props) {
super(props);
this.state = {
dynamicTranslate: this.props.dynamicTranslate,
};
}
// I've tried this method as both sync and async (with await) but neither work
googleTranslate = (key) => {
const translator = TranslatorFactory.createTranslator();
// translate returns a Promise
return translator.translate(key, i18n.locale)
.then((response) => {return response});
}
renderText() {
// getting some values....
// this loops through all the feedback information
for (var i = 0; i < components_feedback.length; i++) {
let label = (some string);
let value = (some string);
// to do: call google translate call here if Boolean(this.state.dynamicTranslate)
if (Boolean(this.state.dynamicTranslate)) {
// I am ultimately trying to save the translation string from googleTranslate()
// in label/value so I can push it into feedbacks
label = this.googleTranslate(label);
value = this.googleTranslate(value);
}
feedbacks.push({label: label, value: value, type: comp.type})
}
return (
// some stuff
feedbacks.map((feedback, index)) => {
// some stuff
<Text>{feedback.label}</Text>
<Text>{feedback.value}</Text>
// some other stuff
});
);
}
render() {
return (
<View>{this.renderText()}</View>
);
}
One of the issues I'm running into is that label/value is a Promise if translation is on. If I try to make renderText() an async method, it is also turned into a Promise which render() can't handle. No idea where to go from here.
Solved this issue. Solution is to put the loop in an async function that (ideally) gets called on construction. This loop was edited to await the returns and push to local arrays of labels and values then saves those in state. You can compare the length of those arrays to the expected length (compare length of last array being used to be positive that it has finished) and that is how you can know if the Promises have returned. Paraphrased code:
constructor(props) {
this.state = {
translatedLabels = []
translatedValues = []
}
this.asyncFunction()
}
asyncFunction = () => {
labels = []
for loop
label = await promise
labels.push(label)
//same for values
end for
this.setState({translatedLabels: labels})
}
//later
renderText() {
if (this.state.translatedLabels.length === whatever) {
// do your stuff as you know the async function has finished
}
}
render() {
return (
{this.renderText()}
);
}

Locking a React Native App After an Amount of Time

I am working on a React Native application which shows a pincode screen upon login and each time the app is started (no longer active in the background). I would like to display this pincode screen each time a user leaves the app in the background longer than 5 minutes. This app will be deployed on Android and iOS.
Looking at search results shows me options for third-party libraries that may offer this functionality. Is there a way to do this without needing to resort to third-party tooling?
You could try to implement AppState change event
AppState.addEventListener('change', onAppStateChange);
let lastTimestamp = 0;
function onAppStateChange(state) {
if (state === 'background') {
// Store current time
lastTimestamp = Date.now();
} else if (state === 'active') {
if (Date.now - lastTimestamp > 5 * 60 * 1000) {
// Show you pin code screen
}
}
}
// Remember to remove listener when you don't need it
AppState.removeEventListener('change', onAppStateChange);
You can use PanResponder
import PanResponder from react-native
Add below inside your constructor
this. panResponser = PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
let now = new Date().getTime();
if (now < your_desired_expiry_time) {
let newTime = new Date().getTime() + your_logout_time_in_milliseconds;
//now you can update your expiry time with newTime
}
else {
//go back to login
}
}
})
Usage
<View {...this.panResponser.panHandlers}>
...your other views
</View>