How to properly test onScroll event value in React Native? - react-native

My code has a lot of animations that run off the same animationRange generated from the same FlatList onScroll. They all work as intended, but I need to add test coverage to where the animations are being invoked. Unfortunately, I am unaware of how to mock the animationRange.
I have tried mocking animationRange with something like this:
const mockInterpolation = jest.fn();
const mockAnimationRange = jest.fn().mockImplementation(() => {
return { interpolate: mockInterpolation }
});
but it's just returning an error when testing and I'm confident it's just because I don't know how to properly mock this value.
Here is the Flatlist that sets the animationRange
--------------------
<FlatList
onScroll={Animated.event([{
nativeEvent: { contentOffset: { y: animationRange } },
}],
{
useNativeDriver: true,
})}
/>
This animationRange value appears to be what I need to mock. And it needs to be able to handle the following:
export const AnimateScore = (animationRange) => {
return {
transform: [
{
translateX: animationRange.interpolate({
inputRange: [0, (100)],
outputRange: [0, screenWidth * 0.125)],
extrapolate: 'clamp',
}),
},
],
};
};
All I am looking for is the proper way to mock this value or test .interpolate for the onScroll's offset value.
Thank you in advance.

Related

React Native Animated If function

const OFFSET = useRef(new Animated.Value(0)).current;
const LOGO_ANIM_INTERPOLATE = OFFSET.interpolate({
inputRange: [-100,0],
outputRange: [ WINDOW_WIDTH/2-35,0],
extrapolate: 'clamp',
})
if (LOGO_ANIM_INTERPOLATE === WINDOW_WIDTH/2-35) {
alert('OK');
} else {
}
-----------
OnScroll in ScrollView
onScroll={
Animated.event(
[{nativeEvent: {
contentOffset: {
y: OFFSET,
},
},
}],
{ useNativeDriver: false },
)}
--------
An object move to the right accordingly Scroll y-offset.
If object get x-position = WINDOW_WIDTH/2-35 I want alert('OK');
IF function not working! Why?

Set duration and delay when using withSpring in reanimated

I am very new to reanimated and have it working to a certain extent but I can not seem to add the duration of the animation. I would also like to add a delay but that is not as important as the duration. I am trying to change the opacity and its Y position. I can change the duration of the opacity but not the Y position. I have tried messing with the settings like damping, stiffness etc but that does not change the actual duration.
Am I using it in the wrong way?
I am currently trying this :
const offset = useSharedValue(400);
const imgOpacity= useSharedValue(0);
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY: withSpring(offset.value, { mass: 0.1, damping: 100, stiffness: 100 }),
},
],
opacity: withTiming(imgOpacity.value, { duration: 1000 }),
};
});
I am changing the offset like this :
React.useEffect(() => {
if (show) {
offset.value = withSpring(10);
imgOpacity.value =1;
} else {
// alert("No Show")
}
}, [show]);
I have tried this to add withTiming but it is basically the same. Any advice would be appreciated.
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY : withTiming(offset.value, {
duration: 1,
easing: Easing.bezier(0.25, 0.1, 0.25, 1),
}),
},
],
opacity: withTiming(imgOpacity.value, { duration: 1000 }),
};
});
The element I am trying to animate is this, is only has 3 images in it.
<Animated.View style={[animatedStyle]}>
<Image style={styles.BImage} source={{ uri: imgb }} />
<Image style={styles.AImage} source={{ uri: imga }} />
<Image style={styles.CImage} source={{ uri: imgc }} />
</Animated.View>
I have managed to achieve this but I am not sure this is the only and best way to do it. I added a config that is added to the Timing animation that adds a away to control the timing. I have also managed to add a delay before animation starts. I have added this in case anyone is starting out with reanimated and is having the same issue.
The code :
const offset = useSharedValue(90); // The offset is the Y value
const config = { // To control the lengh of the animation
duration: 500,
easing: Easing.bounce,
};
const animatedImg = useAnimatedStyle(() => { // The actual animation
return {
transform: [{ translateY: withTiming(offset.value, config) }],
};
});
<Animated.View style={[animatedImg2]}> // Object to animate
<Image
style={[styles.BImage]}
source={{
uri: 'https://image.tmdb.org/t/p/w440_and_h660_face/t6HIqrRAclMCA60NsSmeqe9RmNV.jpg',
}}
/>
</Animated.View>
The animated object can be moved by settting
offset.value = 200 // Method to change the Y value
Too add a delay before animation :
transform: [{ translateY: withDelay(1000, withTiming(offset.value, config)) }],

Expo React Native - How to animate an icon onpress?

I want an icon to bounce (or other animations) when I click the icon. Im trying to implement it looking at the example on expo but nothing works and I also have problems understanding:
interpolate : how do I know what numbers I need for inputRange&outputRange? I cannot find any info about this.
I also dont really get how to use animatedStyles in order to achieve the bounce effect.
Ive looked up tutorials but I still dont get it. An help would be really great. Thank you!!
import { Ionicons } from '#expo/vector-icons';
const AnimatedIconComponent = Animated.createAnimatedComponent(Ionicons);
const AnimatedIcon = () => {
let bounce = new Animated.Value(60);
const animate = () => {
bounce.setValue(0);
Animated.timing(bounce,
{
toValue: 100,
duration: 1200,
easing: Easing.bounce,
useNativeDriver: true
})
.start()
}
const size = bounce.interpolate({
inputRange: [0, 1],
outputRange: [0, 80]
});
const animatedStyles = [
styles.icon,
{
bounce,
width: size,
height: size
}
];
return (
<View>
<TouchableHighlight onPress={()=> animate()}>
<AnimatedIconComponent
name="checkmark-circle-outline"
size={64} style={animatedStyles}
/>
</TouchableHighlight>
</View>
)
};
export default AnimatedIcon;

React Native: Error while updating property 'transform' of a view managed by: RCTView in Android

I am developing some application with React Native. First, I was checking Android and IOS at the same time. After that, I made a mistake. I continued with only IOS. After a while, I had run in Android Emulator then I saw the app is crashed (Error is attached). I doubt the animations. I will not add all the codes of the project to not waste anyone's time. I have added the codes of animated CardFlip. It gives the error when only I opened the page which has the animated component. I hope that somebody might help me with this.
CardFlip Component
import React, { ReactNode, useEffect, useState } from 'react';
import { Animated, StyleSheet, ViewStyle, View, TouchableWithoutFeedback } from 'react-native';
import { isAndroid } from 'Utils/Device';
import { Helpers } from 'Theme/index';
interface CardFlipProps {
condition?: boolean
children: [ReactNode, ReactNode]
containerStyle?: ViewStyle|Array<ViewStyle>
}
const CardFlip: React.FunctionComponent<CardFlipProps> = ({ children, condition = true, containerStyle = {} }): JSX.Element => {
const [flip, setFlip] = useState(false);
const [rotate] = useState(new Animated.Value(0));
const startAnimation = ({ toRotate }) => {
Animated.parallel([
Animated.spring(rotate, {
toValue: toRotate,
friction: 8,
tension: 1,
}),
]).start();
};
useEffect(() => {
if (flip) {
startAnimation({ toRotate: 1 });
} else {
startAnimation({ toRotate: 0 });
}
}, [flip]);
useEffect(() => {
if (!condition) {
setFlip(false);
}
}, [condition]);
const interpolatedFrontRotate = rotate.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '180deg'],
});
const interpolatedBackRotate = rotate.interpolate({
inputRange: [0, 1],
outputRange: ['180deg', '0deg'],
});
const frontOpacity = rotate.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.7],
});
const backOpacity = rotate.interpolate({
inputRange: [0, 1],
outputRange: [0.7, 1],
});
const perspective = isAndroid() ? undefined : 1000;
return (
<TouchableWithoutFeedback onPress={ () => condition && setFlip(!flip) }>
<View style={ containerStyle }>
<Animated.View style={ [
style.card,
{ transform: [{ rotateY: interpolatedFrontRotate }, { perspective }] },
{ opacity: frontOpacity },
] }>
{ children[0] }
</Animated.View>
<Animated.View style={ [
style.card,
{ transform: [{ rotateY: interpolatedBackRotate }, { perspective }] },
{ opacity: backOpacity },
] }>
{ children[1] }
</Animated.View>
</View>
</TouchableWithoutFeedback>
);
};
interface Styles {
card: ViewStyle
}
const style = StyleSheet.create<Styles>({
card: {
...StyleSheet.absoluteFillObject,
width: '100%',
height: '100%',
backfaceVisibility: 'hidden',
...Helpers.center,
},
});
export default CardFlip;
Just moving my comment as answer as requested. It seems that the perspective value could be null or a bug in Android so either removing it or using it conditionally should work.
in the lines
{ transform: [{ rotateY: interpolatedFrontRotate }, { perspective }] }, and
{ transform: [{ rotateY: interpolatedBackRotate }, { perspective }] }, : removing perspective or implementing it as { transform: [{ rotateY: interpolatedFrontRotate }, { perspective: perspective }] }, may solve your problem. In addition, you can rename the const perspective to avoid confusion.

how to push a new Card onto a StackNavigator in react-navigation without animation?

I'm trying to push a new screen onto a StackNavigator, but without animation. I need the effect to be instant. I'm looking through the docs, but I'm having a hard time discerning how to configure the transition for a StackNavigator. I only need do disable animation for one specific route.
In the StackNavigatorConfig section of this page I see some config objects outlined such as transitionConfig that seem potentially promising..? but how do I find a description of how to use these objects?
According to issue 1120, currently animation cannot be disabled.
And transitionConfig is not well documented, its definition can be found here
export type NavigationTransitionSpec = {
duration?: number,
// An easing function from `Easing`.
easing?: (t: number) => number,
// A timing function such as `Animated.timing`.
timing?: (value: AnimatedValue, config: any) => any,
};
/**
* Describes a visual transition from one screen to another.
*/
export type TransitionConfig = {
// The basics properties of the animation, such as duration and easing
transitionSpec?: NavigationTransitionSpec,
// How to animate position and opacity of the screen
// based on the value generated by the transitionSpec
screenInterpolator?: (props: NavigationSceneRendererProps) => Object,
};
Example FYI:
// custom Modal transition animation
transitionConfig: () => ({
transitionSpec: {
duration: 250,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps
const { index } = scene
const height = layout.initHeight
const translateY = position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [height, 0, 0],
})
const opacity = position.interpolate({
inputRange: [index - 1, index - 0.99, index],
outputRange: [0, 1, 1],
})
return { opacity, transform: [{ translateY }] }
},
}),