I am developing an animation using the react-native-reanimated and react-native-gesture-handler libraries. When I open the page it works as I expected. But when I change any data using fast refresh or hook in the function. The value of posX is being reset. And even though it appears in debug in the event, it does not update the data in Animated.View.
import React, { useState } from 'react';
import { StyleSheet, View, Image, Dimensions } from 'react-native';
import Animated, {
Value,
event,
set,
block,
cond,
add,
eq,
debug,
greaterThan,
lessThan,
multiply,
useCode,
} from 'react-native-reanimated';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
interface Props {}
const R = 70;
const image1 = require('./img1.jpeg');
const image2 = require('./img3.jpeg');
const { width, height } = Dimensions.get('window');
export const SplitView: React.FC<Props> = () => {
const MAX = width - R;
const MIN = 0;
const posX = new Value<number>(0);
const offsetX = new Value<number>((width - R) / 2);
const panState = new Value(State.UNDETERMINED);
const onGestureHandler = event([
{
nativeEvent: ({
translationX,
state,
}: {
translationX: number;
state: State;
}) =>
block([
set(panState, state),
set(posX, add(translationX, offsetX)),
cond(
lessThan(posX, MIN),
set(posX, MIN),
cond(greaterThan(posX, MAX), set(posX, MAX)),
),
debug('posX ', posX), // <=== always show on console
cond(eq(state, State.END), [
set(offsetX, add(offsetX, translationX)),
cond(
lessThan(offsetX, MIN),
set(offsetX, MIN),
cond(greaterThan(offsetX, MAX), set(offsetX, MAX)),
),
]),
]),
},
]);
return (
<View style={styles.container}>
<View style={[styles.left, { width, height }]}>
<Animated.Image style={styles.image} source={image1} />
</View>
// But after value change or run fast refresh not working.
// Initial value gets stuck
<Animated.View
style={[styles.right, { width, height, left: add(posX, R / 2) }]}>
<Animated.Image
style={[styles.image, { left: multiply(add(posX, R / 2), -1) }]}
source={image2}
/>
</Animated.View>
<PanGestureHandler
onGestureEvent={onGestureHandler}
onHandlerStateChange={onGestureHandler}>
<Animated.View
style={[
styles.ball,
{
left: posX,
top: (height - R) / 2,
},
]}
/>
</PanGestureHandler>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'blue',
},
ball: {
width: R,
height: R,
backgroundColor: 'red',
borderRadius: R / 2,
zIndex: 2,
},
image: {
flex: 1,
},
left: {
position: 'absolute',
top: 0,
left: 0,
flex: 1,
},
right: {
position: 'absolute',
flex: 1,
top: 0,
left: 0,
zIndex: 1,
overflow: 'hidden',
backgroundColor: 'yellow',
},
});
Hi @m-inan,
this is probably related to the fact, that Reanimated attach nodes in the native side and they're not detached and reattached after the fast refresh. The best way to avoid weird behavior is to use a full app refresh.
We're not working actively at this but PRs are welcome.
I solved this problem using useMemoOne. Thanks for the answer.
@m-inan where did you apply memoization? could you share a snippet with the code?
@hbarylskyi Hi, example like this
const value = useMemoOne(() => new Animated.Value(), []);
You can use it this way. but I'm doing it this way using the package 'react-native-redash' right now.
import Animated from 'react-native-reanimated';
import { State } from 'react-native-gesture-handler';
import { useValue } from 'react-native-redash';
const value = useValue(0);
// or
const [value, state] = useValues([0, State.UNDETERMINED])
We have PR #786 which will be likely merged in the near future, so you'll be able to use it directly from Reanimated as well
Basically we need to use useRef/useMemo hooks to preserve references to registered nodes between rerenders
Most helpful comment
@hbarylskyi Hi, example like this
You can use it this way. but I'm doing it this way using the package 'react-native-redash' right now.