Q&A
How to create animations using React Native Reanimated
Overview
Standard React Native's Animated API often runs animation logic on the JavaScript thread, which can stutter under load. Reanimated moves animations to the native UI thread using "worklets" (functions that run natively), delivering 60fps animations decoupled from JS work. The current API is hook-based and declarative.
graph LR
A[useSharedValue: animated value] --> B[useAnimatedStyle: derive style]
B --> C[Apply to Animated.View]
A --> D[Update via withTiming / withSpring]
D --> E[Runs on UI thread → smooth 60fps]
style E fill:#1e1b4b,color:#fff
Core Building Blocks
useSharedValue(initial)- a value that lives on the UI thread and can be animated; changing.valuedrives the animation.useAnimatedStyle(() => ({...}))- a worklet that returns a style object derived from shared values; it re-runs natively when they change.- Animation functions -
withTiming,withSpring,withDecay,withRepeat,withSequence,withDelay.
Basic Example: Fade + Move
import Animated, {
useSharedValue, useAnimatedStyle, withTiming, withSpring,
} from "react-native-reanimated";
import { Button, View } from "react-native";
function Box() {
const offset = useSharedValue(0);
const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ translateX: offset.value }],
opacity: opacity.value,
}));
const move = () => {
offset.value = withSpring(offset.value === 0 ? 150 : 0); // spring physics
opacity.value = withTiming(opacity.value === 1 ? 0.3 : 1, { duration: 300 });
};
return (
<View>
<Animated.View style={[{ width: 80, height: 80, backgroundColor: "#6366f1" }, animatedStyle]} />
<Button title="Animate" onPress={move} />
</View>
);
}
Assigning withSpring(...)/withTiming(...) to a shared value's .value automatically animates it on the UI thread; useAnimatedStyle maps those values to styles.
Animation Helpers
| Function | Effect |
|---|---|
withTiming(to, {duration, easing}) |
Linear/eased transition over a duration |
withSpring(to, {damping, stiffness}) |
Natural spring physics |
withRepeat(anim, count, reverse) |
Repeat an animation (use -1 for infinite) |
withSequence(a, b, c) |
Run animations one after another |
withDelay(ms, anim) |
Delay before starting |
// pulsing loop
scale.value = withRepeat(withTiming(1.2, { duration: 600 }), -1, true);
Gestures and Scroll
Reanimated pairs with react-native-gesture-handler for drag/pan, and offers useAnimatedScrollHandler for scroll-driven animations and useAnimatedGestureHandler/the modern Gesture API for interactive, UI-thread-driven gestures - all without round-tripping to JS each frame.
Worklets (the key concept)
Functions marked as worklets (the body of useAnimatedStyle, or annotated with "worklet") are compiled to run on the UI thread. This is why Reanimated is fast - the animation logic doesn't wait on the JS thread. To call back into JS from a worklet, use runOnJS(...).
Why This Matters in Interviews
React Native interviews use this to test performance awareness. The headline: Reanimated runs animations on the UI thread via worklets, so they don't stutter when JS is busy (the core advantage over the classic Animated API). Know the v2/v3 hook trio (useSharedValue, useAnimatedStyle, withTiming/withSpring), that updating .value drives animation, and runOnJS to bridge back. Mentioning gesture-handler integration for interactive animations shows depth.
Common Pitfalls
- Reading/writing shared values'
.valueduring render (do it in worklets/handlers, not the render body). - Forgetting
runOnJSwhen calling JS functions (likesetState) from a worklet. - Mixing the old
AnimatedAPI with Reanimated unnecessarily. - Not wrapping the app/components per the library's Babel-plugin setup (worklets won't compile).
- Heavy JS work inside worklets, defeating the UI-thread benefit.
References
- React Native Reanimated docs: https://docs.swmansion.com/react-native-reanimated/
- Gesture Handler: https://docs.swmansion.com/react-native-gesture-handler/