A framework for building native apps using React
Overall
score
100%
Evaluation — 100%
↑ 1.06xAgent success when using this tile
React Native provides powerful animation and interaction APIs for creating smooth, engaging user experiences with declarative animations and gesture handling.
npm install react-nativeThe primary animation library providing declarative, composable animations with performance optimization.
// ESM
import {Animated} from 'react-native';
// CommonJS
const {Animated} = require('react-native');
// Basic animated value
const fadeAnim = new Animated.Value(0);
// Animate to value
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver: true, // Use native driver for performance
}).start();
// Basic fade in animation
function FadeInView({children, style, ...props}) {
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}, [fadeAnim]);
return (
<Animated.View
style={[style, {opacity: fadeAnim}]}
{...props}
>
{children}
</Animated.View>
);
}
// Multiple animated values
function SlideUpView({children}) {
const slideAnim = useRef(new Animated.Value(50)).current;
const fadeAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.parallel([
Animated.timing(slideAnim, {
toValue: 0,
duration: 500,
useNativeDriver: true,
}),
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
]).start();
}, []);
return (
<Animated.View
style={{
opacity: fadeAnim,
transform: [{translateY: slideAnim}],
}}
>
{children}
</Animated.View>
);
}
// Animated components
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
// Scale animation on press
function ScaleButton({children, onPress}) {
const scaleAnim = useRef(new Animated.Value(1)).current;
const handlePressIn = () => {
Animated.spring(scaleAnim, {
toValue: 0.95,
useNativeDriver: true,
}).start();
};
const handlePressOut = () => {
Animated.spring(scaleAnim, {
toValue: 1,
useNativeDriver: true,
}).start();
};
return (
<AnimatedTouchable
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onPress={onPress}
style={{
transform: [{scale: scaleAnim}],
}}
>
{children}
</AnimatedTouchable>
);
}
// Interpolation for complex animations
function RotatingView({children}) {
const rotateAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.loop(
Animated.timing(rotateAnim, {
toValue: 1,
duration: 2000,
useNativeDriver: true,
})
).start();
}, []);
const rotate = rotateAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
return (
<Animated.View style={{transform: [{rotate}]}}>
{children}
</Animated.View>
);
}
// Color interpolation
function ColorChangingView() {
const colorAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(colorAnim, {
toValue: 1,
duration: 1000,
useNativeDriver: false, // Color animations need JS driver
}),
Animated.timing(colorAnim, {
toValue: 0,
duration: 1000,
useNativeDriver: false,
}),
])
).start();
}, []);
const backgroundColor = colorAnim.interpolate({
inputRange: [0, 1],
outputRange: ['rgb(255, 0, 0)', 'rgb(0, 0, 255)'],
});
return (
<Animated.View style={{backgroundColor, width: 100, height: 100}} />
);
}
// Chained animations
function ChainedAnimation() {
const translateX = useRef(new Animated.Value(0)).current;
const scale = useRef(new Animated.Value(1)).current;
const startAnimation = () => {
Animated.sequence([
Animated.timing(translateX, {
toValue: 100,
duration: 500,
useNativeDriver: true,
}),
Animated.spring(scale, {
toValue: 1.5,
useNativeDriver: true,
}),
Animated.delay(500),
Animated.parallel([
Animated.timing(translateX, {
toValue: 0,
duration: 500,
useNativeDriver: true,
}),
Animated.spring(scale, {
toValue: 1,
useNativeDriver: true,
}),
]),
]).start();
};
return (
<View>
<Animated.View
style={{
transform: [
{translateX},
{scale},
],
}}
>
<Text>Animated Box</Text>
</Animated.View>
<Button title="Start Animation" onPress={startAnimation} />
</View>
);
}
// Value listeners
function AnimationWithCallback() {
const slideAnim = useRef(new Animated.Value(0)).current;
const animate = () => {
slideAnim.setValue(0);
Animated.timing(slideAnim, {
toValue: 100,
duration: 1000,
useNativeDriver: true,
}).start((finished) => {
if (finished) {
console.log('Animation completed');
} else {
console.log('Animation was interrupted');
}
});
};
// Listen to value changes
useEffect(() => {
const listener = slideAnim.addListener(({value}) => {
console.log('Current value:', value);
});
return () => slideAnim.removeListener(listener);
}, []);
return (
<View>
<Animated.View
style={{
transform: [{translateX: slideAnim}],
}}
>
<Text>Moving Box</Text>
</Animated.View>
<Button title="Animate" onPress={animate} />
</View>
);
}// Animated Value types
interface AnimatedValue {
constructor(value: number): AnimatedValue;
setValue(value: number): void;
setOffset(offset: number): void;
flattenOffset(): void;
extractOffset(): void;
addListener(callback: (value: {value: number}) => void): string;
removeListener(id: string): void;
removeAllListeners(): void;
stopAnimation(callback?: (value: number) => void): void;
resetAnimation(callback?: (value: number) => void): void;
interpolate(config: InterpolationConfig): AnimatedValue;
}
interface InterpolationConfig {
inputRange: number[];
outputRange: number[] | string[];
easing?: (input: number) => number;
extrapolate?: 'extend' | 'identity' | 'clamp';
extrapolateLeft?: 'extend' | 'identity' | 'clamp';
extrapolateRight?: 'extend' | 'identity' | 'clamp';
}
// Animation configuration types
interface TimingAnimationConfig {
toValue: number;
easing?: (input: number) => number;
duration?: number;
delay?: number;
useNativeDriver?: boolean;
isInteraction?: boolean;
}
interface SpringAnimationConfig {
toValue: number;
restDisplacementThreshold?: number;
overshootClamping?: boolean;
restSpeedThreshold?: number;
velocity?: number;
bounciness?: number;
speed?: number;
tension?: number;
friction?: number;
stiffness?: number;
damping?: number;
mass?: number;
useNativeDriver?: boolean;
isInteraction?: boolean;
}
interface DecayAnimationConfig {
velocity: number;
deceleration?: number;
isInteraction?: boolean;
useNativeDriver?: boolean;
}
// Animation composition types
interface CompositeAnimation {
start(callback?: (finished: boolean) => void): void;
stop(): void;
reset(): void;
}
// Animated component types
interface AnimatedStatic {
// Core values
Value: typeof AnimatedValue;
ValueXY: typeof AnimatedValueXY;
// Animated components
View: React.ComponentType<Animated.AnimatedProps<ViewProps>>;
Text: React.ComponentType<Animated.AnimatedProps<TextProps>>;
Image: React.ComponentType<Animated.AnimatedProps<ImageProps>>;
ScrollView: React.ComponentType<Animated.AnimatedProps<ScrollViewProps>>;
FlatList: React.ComponentType<Animated.AnimatedProps<FlatListProps<any>>>;
SectionList: React.ComponentType<Animated.AnimatedProps<SectionListProps<any>>>;
// Animation methods
timing(value: AnimatedValue, config: TimingAnimationConfig): CompositeAnimation;
spring(value: AnimatedValue, config: SpringAnimationConfig): CompositeAnimation;
decay(value: AnimatedValue, config: DecayAnimationConfig): CompositeAnimation;
// Composition methods
parallel(animations: CompositeAnimation[], config?: {stopTogether?: boolean}): CompositeAnimation;
sequence(animations: CompositeAnimation[]): CompositeAnimation;
stagger(time: number, animations: CompositeAnimation[]): CompositeAnimation;
delay(time: number): CompositeAnimation;
loop(animation: CompositeAnimation, config?: {iterations?: number}): CompositeAnimation;
// Utility methods
add(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
subtract(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
divide(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
multiply(a: AnimatedValue, b: AnimatedValue): AnimatedValue;
modulo(a: AnimatedValue, modulus: number): AnimatedValue;
diffClamp(a: AnimatedValue, min: number, max: number): AnimatedValue;
// Component creation
createAnimatedComponent<T extends React.ComponentType<any>>(component: T): React.ComponentType<Animated.AnimatedProps<React.ComponentProps<T>>>;
// Events
event(argMapping: any[], config?: {useNativeDriver?: boolean; listener?: Function}): Function;
}
namespace Animated {
interface AnimatedProps<T> {
[K in keyof T]: K extends 'style'
? StyleProp<T[K]> | AnimatedValue | {[P in keyof T[K]]: T[K][P] | AnimatedValue}
: T[K];
}
}Easing functions for smooth animation transitions and natural motion curves.
import {Easing} from 'react-native';
// Basic easing functions
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
easing: Easing.ease, // Default easing
useNativeDriver: true,
}).start();
};
// Different easing curves
const easingExamples = {
// Basic curves
linear: Easing.linear,
ease: Easing.ease,
quad: Easing.quad,
cubic: Easing.cubic,
// Directional easing
easeIn: Easing.in(Easing.quad),
easeOut: Easing.out(Easing.quad),
easeInOut: Easing.inOut(Easing.quad),
// Bezier curves
customBezier: Easing.bezier(0.25, 0.1, 0.25, 1),
// Bounce and elastic
bounce: Easing.bounce,
elastic: Easing.elastic(1),
// Stepped animation
steps: Easing.step0,
// Circle easing
circle: Easing.circle,
// Sine easing
sine: Easing.sin,
// Exponential easing
expo: Easing.exp,
// Back easing (overshoot)
back: Easing.back(1.5),
// Polynomial easing
poly: Easing.poly(4),
};
// Custom easing function
const customEasing = (t) => {
return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
};
// Easing comparison component
function EasingComparison() {
const animations = useRef(
Object.keys(easingExamples).reduce((acc, key) => {
acc[key] = new Animated.Value(0);
return acc;
}, {})
).current;
const startAnimations = () => {
Object.keys(easingExamples).forEach((key) => {
animations[key].setValue(0);
Animated.timing(animations[key], {
toValue: 200,
duration: 2000,
easing: easingExamples[key],
useNativeDriver: true,
}).start();
});
};
return (
<View>
<Button title="Start Easing Comparison" onPress={startAnimations} />
{Object.keys(easingExamples).map((key) => (
<View key={key} style={styles.easingRow}>
<Text style={styles.easingLabel}>{key}</Text>
<Animated.View
style={[
styles.easingBox,
{transform: [{translateX: animations[key]}]},
]}
/>
</View>
))}
</View>
);
}
// Bouncy button with back easing
function BouncyButton({children, onPress}) {
const scaleAnim = useRef(new Animated.Value(1)).current;
const handlePress = () => {
Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 0.8,
duration: 100,
easing: Easing.in(Easing.back(2)),
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 300,
easing: Easing.out(Easing.back(2)),
useNativeDriver: true,
}),
]).start();
onPress?.();
};
return (
<AnimatedTouchable
onPress={handlePress}
style={{
transform: [{scale: scaleAnim}],
}}
>
{children}
</AnimatedTouchable>
);
}
// Progress indicator with stepped animation
function SteppedProgress({progress}) {
const stepAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(stepAnim, {
toValue: progress,
duration: 1000,
easing: Easing.step0, // Stepped animation
useNativeDriver: false,
}).start();
}, [progress]);
const width = stepAnim.interpolate({
inputRange: [0, 1],
outputRange: ['0%', '100%'],
});
return (
<View style={styles.progressContainer}>
<Animated.View style={[styles.progressBar, {width}]} />
</View>
);
}interface EasingStatic {
// Basic easing functions
linear: (t: number) => number;
ease: (t: number) => number;
quad: (t: number) => number;
cubic: (t: number) => number;
poly(n: number): (t: number) => number;
sin: (t: number) => number;
circle: (t: number) => number;
exp: (t: number) => number;
// Directional modifiers
in(easing: (t: number) => number): (t: number) => number;
out(easing: (t: number) => number): (t: number) => number;
inOut(easing: (t: number) => number): (t: number) => number;
// Special easing functions
elastic(bounciness?: number): (t: number) => number;
back(s?: number): (t: number) => number;
bounce: (t: number) => number;
// Bezier curve
bezier(x1: number, y1: number, x2: number, y2: number): (t: number) => number;
// Stepped animation
step0: (t: number) => number;
step1: (t: number) => number;
}Automatically animate layout changes without explicit animation code.
import {LayoutAnimation} from 'react-native';
// Basic layout animation
function AnimatedList() {
const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);
const addItem = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setItems([...items, `Item ${items.length + 1}`]);
};
const removeItem = (index) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
setItems(items.filter((_, i) => i !== index));
};
return (
<View>
<Button title="Add Item" onPress={addItem} />
{items.map((item, index) => (
<TouchableOpacity
key={index}
onPress={() => removeItem(index)}
style={styles.listItem}
>
<Text>{item}</Text>
</TouchableOpacity>
))}
</View>
);
}
// Custom layout animation config
const customLayoutAnimation = {
duration: 300,
create: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update: {
type: LayoutAnimation.Types.easeInEaseOut,
},
delete: {
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
};
function CustomAnimatedComponent() {
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpanded = () => {
LayoutAnimation.configureNext(customLayoutAnimation);
setIsExpanded(!isExpanded);
};
return (
<View>
<TouchableOpacity onPress={toggleExpanded} style={styles.header}>
<Text>Tap to expand</Text>
</TouchableOpacity>
{isExpanded && (
<View style={styles.content}>
<Text>This content animates in and out</Text>
<Text>Using LayoutAnimation</Text>
</View>
)}
</View>
);
}
// Grid layout animation
function AnimatedGrid() {
const [numColumns, setNumColumns] = useState(2);
const toggleColumns = () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring);
setNumColumns(numColumns === 2 ? 3 : 2);
};
return (
<View>
<Button
title={`Switch to ${numColumns === 2 ? 3 : 2} columns`}
onPress={toggleColumns}
/>
<FlatList
data={Array.from({length: 20}, (_, i) => ({id: i, title: `Item ${i}`}))}
numColumns={numColumns}
key={numColumns} // Force re-render for column change
renderItem={({item}) => (
<View style={[styles.gridItem, {flex: 1/numColumns}]}>
<Text>{item.title}</Text>
</View>
)}
/>
</View>
);
}
// Layout animation with callbacks
function CallbackLayoutAnimation() {
const [items, setItems] = useState(['A', 'B', 'C']);
const shuffleItems = () => {
LayoutAnimation.configureNext(
LayoutAnimation.Presets.easeInEaseOut,
() => {
console.log('Layout animation finished');
},
(error) => {
console.error('Layout animation failed:', error);
}
);
const shuffled = [...items].sort(() => Math.random() - 0.5);
setItems(shuffled);
};
return (
<View>
<Button title="Shuffle Items" onPress={shuffleItems} />
{items.map((item, index) => (
<View key={item} style={styles.shuffleItem}>
<Text>{item}</Text>
</View>
))}
</View>
);
}
// Enable layout animations on Android
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}interface LayoutAnimationStatic {
// Configure next layout change
configureNext(
config: LayoutAnimationConfig,
onAnimationDidEnd?: () => void,
onAnimationDidFail?: (error: any) => void
): void;
// Preset configurations
Presets: {
easeInEaseOut: LayoutAnimationConfig;
linear: LayoutAnimationConfig;
spring: LayoutAnimationConfig;
};
// Animation types
Types: {
spring: string;
linear: string;
easeInEaseOut: string;
easeIn: string;
easeOut: string;
keyboard: string;
};
// Animation properties
Properties: {
opacity: string;
scaleX: string;
scaleY: string;
scaleXY: string;
};
}
interface LayoutAnimationConfig {
duration?: number;
create?: LayoutAnimationAnim;
update?: LayoutAnimationAnim;
delete?: LayoutAnimationAnim;
}
interface LayoutAnimationAnim {
duration?: number;
delay?: number;
springDamping?: number;
initialVelocity?: number;
type?: string;
property?: string;
}Handle complex gesture recognition and touch events for custom interactive components.
import {PanResponder} from 'react-native';
// Basic draggable component
function DraggableView({children}) {
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
x: pan.x._value,
y: pan.y._value,
});
},
onPanResponderMove: Animated.event([
null,
{dx: pan.x, dy: pan.y},
], {useNativeDriver: false}),
onPanResponderRelease: () => {
pan.flattenOffset();
},
})
).current;
return (
<Animated.View
style={{
transform: [{translateX: pan.x}, {translateY: pan.y}],
}}
{...panResponder.panHandlers}
>
{children}
</Animated.View>
);
}
// Swipe-to-dismiss component
function SwipeToDismiss({children, onDismiss}) {
const translateX = useRef(new Animated.Value(0)).current;
const [dismissed, setDismissed] = useState(false);
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (_, gestureState) => {
return Math.abs(gestureState.dx) > Math.abs(gestureState.dy);
},
onPanResponderMove: (_, gestureState) => {
translateX.setValue(gestureState.dx);
},
onPanResponderRelease: (_, gestureState) => {
const threshold = 120;
if (Math.abs(gestureState.dx) > threshold) {
// Dismiss
const toValue = gestureState.dx > 0 ? 300 : -300;
Animated.timing(translateX, {
toValue,
duration: 200,
useNativeDriver: true,
}).start(() => {
setDismissed(true);
onDismiss?.();
});
} else {
// Return to original position
Animated.spring(translateX, {
toValue: 0,
useNativeDriver: true,
}).start();
}
},
})
).current;
if (dismissed) return null;
return (
<Animated.View
style={{
transform: [{translateX}],
}}
{...panResponder.panHandlers}
>
{children}
</Animated.View>
);
}
// Scalable and rotatable view
function ScalableRotatableView({children}) {
const scale = useRef(new Animated.Value(1)).current;
const rotate = useRef(new Animated.Value(0)).current;
const lastScale = useRef(1);
const lastRotate = useRef(0);
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: () => {
lastScale.current = scale._value;
lastRotate.current = rotate._value;
},
onPanResponderMove: (evt, gestureState) => {
// Multi-touch gestures would require additional logic
// This is a simplified version for single touch
// Scale based on vertical movement
const newScale = lastScale.current + gestureState.dy / 200;
scale.setValue(Math.max(0.5, Math.min(2, newScale)));
// Rotate based on horizontal movement
const newRotate = lastRotate.current + gestureState.dx / 100;
rotate.setValue(newRotate);
},
onPanResponderRelease: () => {
// Optionally snap back to default values
Animated.parallel([
Animated.spring(scale, {
toValue: 1,
useNativeDriver: true,
}),
Animated.spring(rotate, {
toValue: 0,
useNativeDriver: true,
}),
]).start();
},
})
).current;
const rotateStr = rotate.interpolate({
inputRange: [-1, 1],
outputRange: ['-45deg', '45deg'],
});
return (
<Animated.View
style={{
transform: [
{scale},
{rotate: rotateStr},
],
}}
{...panResponder.panHandlers}
>
{children}
</Animated.View>
);
}
// Pull-to-refresh with PanResponder
function CustomPullToRefresh({children, onRefresh}) {
const translateY = useRef(new Animated.Value(0)).current;
const [isRefreshing, setIsRefreshing] = useState(false);
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: (_, gestureState) => {
return gestureState.dy > 0 && gestureState.vy > 0;
},
onPanResponderMove: (_, gestureState) => {
if (gestureState.dy > 0 && !isRefreshing) {
translateY.setValue(Math.min(gestureState.dy, 100));
}
},
onPanResponderRelease: (_, gestureState) => {
if (gestureState.dy > 60 && !isRefreshing) {
setIsRefreshing(true);
Animated.timing(translateY, {
toValue: 60,
duration: 200,
useNativeDriver: true,
}).start();
// Simulate refresh
onRefresh?.().finally(() => {
setIsRefreshing(false);
Animated.timing(translateY, {
toValue: 0,
duration: 300,
useNativeDriver: true,
}).start();
});
} else {
Animated.timing(translateY, {
toValue: 0,
duration: 200,
useNativeDriver: true,
}).start();
}
},
})
).current;
return (
<View style={{flex: 1}}>
<Animated.View
style={{
position: 'absolute',
top: -50,
left: 0,
right: 0,
height: 50,
justifyContent: 'center',
alignItems: 'center',
transform: [{translateY}],
}}
>
{isRefreshing ? (
<ActivityIndicator size="small" />
) : (
<Text>Pull to refresh</Text>
)}
</Animated.View>
<Animated.View
style={{
flex: 1,
transform: [{translateY}],
}}
{...panResponder.panHandlers}
>
{children}
</Animated.View>
</View>
);
}interface PanResponderStatic {
create(config: PanResponderConfig): PanResponderInstance;
}
interface PanResponderInstance {
panHandlers: {
onStartShouldSetResponder: (evt: GestureResponderEvent) => boolean;
onMoveShouldSetResponder: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
onResponderGrant: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onResponderMove: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onResponderRelease: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onResponderTerminate: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onStartShouldSetResponderCapture: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
onMoveShouldSetResponderCapture: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
};
}
interface PanResponderConfig {
onStartShouldSetPanResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
onMoveShouldSetPanResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
onPanResponderGrant?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onPanResponderMove?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onPanResponderRelease?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onPanResponderTerminate?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onPanResponderReject?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onStartShouldSetPanResponderCapture?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
onMoveShouldSetPanResponderCapture?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
onPanResponderStart?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onPanResponderEnd?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => void;
onShouldBlockNativeResponder?: (evt: GestureResponderEvent, gestureState: PanResponderGestureState) => boolean;
}
interface PanResponderGestureState {
stateID: number;
moveX: number;
moveY: number;
x0: number;
y0: number;
dx: number;
dy: number;
vx: number;
vy: number;
numberActiveTouches: number;
}Schedule work after interactions have completed to maintain smooth animations and user experience.
import {InteractionManager} from 'react-native';
// Defer expensive operations after interactions
function ExpensiveComponent() {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Show loading state immediately
setIsLoading(true);
// Wait for interactions to complete, then load data
const interactionPromise = InteractionManager.runAfterInteractions(() => {
// Expensive operation
return fetchLargeDataset().then(result => {
setData(result);
setIsLoading(false);
});
});
return () => {
interactionPromise.cancel();
};
}, []);
if (isLoading) {
return <ActivityIndicator size="large" />;
}
return (
<FlatList
data={data}
renderItem={({item}) => <ExpensiveListItem item={item} />}
/>
);
}
// Navigation transition with deferred loading
function NavigationScreen({route}) {
const [content, setContent] = useState(null);
useEffect(() => {
// Run after navigation animation completes
InteractionManager.runAfterInteractions(() => {
loadScreenContent(route.params.id).then(setContent);
});
}, [route.params.id]);
return (
<View style={styles.container}>
<Header title="Screen Title" />
{content ? (
<Content data={content} />
) : (
<LoadingSkeleton />
)}
</View>
);
}
// Custom hook for deferred execution
function useAfterInteractions(callback, deps = []) {
const [isReady, setIsReady] = useState(false);
useEffect(() => {
let cancelled = false;
const handle = InteractionManager.runAfterInteractions(() => {
if (!cancelled) {
callback();
setIsReady(true);
}
});
return () => {
cancelled = true;
handle.cancel();
};
}, deps);
return isReady;
}
// Usage with custom hook
function DeferredComponent() {
const [heavyData, setHeavyData] = useState(null);
const isReady = useAfterInteractions(() => {
processHeavyData().then(setHeavyData);
}, []);
return (
<View>
{isReady && heavyData ? (
<HeavyDataView data={heavyData} />
) : (
<PlaceholderView />
)}
</View>
);
}
// Creating interaction handles manually
function InteractiveAnimation() {
const scaleAnim = useRef(new Animated.Value(1)).current;
const [isInteracting, setIsInteracting] = useState(false);
const startAnimation = () => {
// Create interaction handle
const handle = InteractionManager.createInteractionHandle();
setIsInteracting(true);
Animated.sequence([
Animated.timing(scaleAnim, {
toValue: 1.2,
duration: 500,
useNativeDriver: true,
}),
Animated.timing(scaleAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
]).start(() => {
// Clear interaction handle when animation completes
InteractionManager.clearInteractionHandle(handle);
setIsInteracting(false);
});
};
return (
<View>
<Animated.View style={{transform: [{scale: scaleAnim}]}}>
<TouchableOpacity onPress={startAnimation}>
<Text>Animate</Text>
</TouchableOpacity>
</Animated.View>
{isInteracting && (
<Text>Animation in progress...</Text>
)}
</View>
);
}
// Batch processing with interaction management
function BatchProcessor({items}) {
const [processedItems, setProcessedItems] = useState([]);
const [isProcessing, setIsProcessing] = useState(false);
const processBatch = async () => {
setIsProcessing(true);
// Process items in batches after interactions
const batchSize = 10;
const batches = [];
for (let i = 0; i < items.length; i += batchSize) {
batches.push(items.slice(i, i + batchSize));
}
for (const batch of batches) {
await new Promise(resolve => {
InteractionManager.runAfterInteractions(() => {
const processed = batch.map(processItem);
setProcessedItems(prev => [...prev, ...processed]);
resolve();
});
});
}
setIsProcessing(false);
};
return (
<View>
<Button
title="Process Items"
onPress={processBatch}
disabled={isProcessing}
/>
<FlatList
data={processedItems}
renderItem={({item}) => <ProcessedItem item={item} />}
/>
{isProcessing && (
<ActivityIndicator style={styles.processingIndicator} />
)}
</View>
);
}interface InteractionManagerStatic {
// Run after interactions
runAfterInteractions(callback: () => void | Promise<any>): {
then: (callback: () => void) => {cancel: () => void};
done: (...args: any[]) => any;
cancel: () => void;
};
// Manual interaction handles
createInteractionHandle(): number;
clearInteractionHandle(handle: number): void;
// Event listeners
addListener?(callback: () => void): void;
// Promise-based API
setDeadline?(deadline: number): void;
}
interface InteractionHandle {
then(callback: () => void): {cancel: () => void};
done(...args: any[]): any;
cancel(): void;
}This comprehensive animation and interaction documentation provides developers with all the tools needed to create smooth, engaging user experiences with React Native's powerful animation APIs and gesture handling capabilities.
Install with Tessl CLI
npx tessl i tessl/npm-react-native@1000.0.0docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10