Is there a cleaner way to organize this recoilJS state? - recoiljs

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.

Related

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

How to correctly use setInterval for multiple functions being called repeatedly in 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 );

Vuex can't stop setInteval

I have a method in Vuex:
export const startTime = ({ commit, state }, status) => {
let timeInterval
if(status) {
const timer = 1000
timeInterval = setInterval(() => {
console.log('x')
commit('SET_RUNNED_TIME', parseInt(state.runnedTime += timer))
}, timer)
}
else {
console.log('y')
clearInterval(timeInterval)
commit('SET_RUNNED_TIME', 0)
}
}
From my component I send value true or false to method startTime(). When I send true then method run commit SET_RUNNED_TIME. But when I send false clearInterval(timeInterval) not stopping setInterval and still is sending commit('SET_RUNNED_TIME' ...)
The variable timeInterval is inside the scope of the function. If you want it to keep on living between different calls to startTime you need to put it outside the function scope. For example in the state.

How can I tell my counter to stop incrementing when it has reached a specific amount?

I'm trying to implement a counter that stops when it has reached a certain amount, but what I have at the moment doesn't stop. It seems to increment continuously; why is this? Does it have something to do with when or how many times useEffect is executed?
const [counter, setCounter] = useState(0);
useEffect(() => {
if (counter < 10) {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, incrementRate);
return () => clearInterval(interval);
}
}, []);
return (
<View>
<Text>Circular progress bar</Text>
<Text>{`${counter}`}</Text>
</View>
);
You're using the useState functional updater inside your effect, which means it will use its previous state to increment the count, which is correct but it gives you a false perception that the effect is working correctly. Your mental model for how useEffect works needs to a shift a little, it's really common, so much so Dan Abramov has written an extensive article on useEffect with this exact scenario.
The quick fix is to add counter to your dependency array, so that you can tell React its changed, so it will no longer skip updating the effect.
useEffect(() => {
if (counter < 10) {
const interval = setInterval(() => {
setCounter((counter) => counter + 1);
}, incrementRate);
return () => {
clearInterval(interval);
};
}
}, [counter]);
It's not the cleanest approach as you'll clear the interval and create a new one each time the effect runs.
You could however use setTimeout instead and avoid the clearing of intervals altogether.
useEffect(() => {
if (counter < 10) {
setTimeout(() => {
setCounter((counter) => counter + 1);
}, incrementRate);
}
}, [counter]);

How to programmatically switch a switch in React Native?

I make us several Switch Components in one view. When I switch on one switch, I want all others to switch off. Currently, I set the boolean value property via the state. This results in changes happen abruptly because the switch is just re-rendered and not transitioned.
So how would you switch them programmatically?
EDIT 2: I just discovered that it runs smoothly on Android so it looks like an iOS-specific problem.
EDIT: part of the code
_onSwitch = (id, switched) => {
let newFilter = { status: null };
if (!switched) {
newFilter = { status: id };
}
this.props.changeFilter(newFilter); // calls the action creator
};
_renderItem = ({ item }) => {
const switched = this.props.currentFilter === item.id; // the state mapped to a prop
return (
<ListItem
switchButton
switched={switched}
onSwitch={() => this._onSwitch(item.id, switched)}
/>
);
};