React Native Reanimated: From Zero to Smooth Animations

May 3, 2024 - 2 min read

The built-in Animated API is fine for simple stuff, but for smooth, complex animations, Reanimated is the way to go.

Why Reanimated?

  • Runs on the UI thread (no JS bridge lag)
  • 60fps animations even with heavy JS workload
  • Gesture handler integration
  • Worklets for custom animation logic

Installation

npx expo install react-native-reanimated

Add the plugin to babel.config.js:

module.exports = {
  presets: ['babel-preset-expo'],
  plugins: ['react-native-reanimated/plugin'],
};

Concept 1: Shared Values

Shared values are like Animated.Value but better:

import { useSharedValue } from 'react-native-reanimated';
 
function MyComponent() {
  const opacity = useSharedValue(0);
  const translateX = useSharedValue(0);
 
  // Update directly - no setState needed
  opacity.value = 1;
  translateX.value = 100;
}

Concept 2: Animated Styles

Convert shared values to styles:

import Animated, { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
 
function FadeBox() {
  const opacity = useSharedValue(0);
 
  const animatedStyle = useAnimatedStyle(() => ({
    opacity: opacity.value,
  }));
 
  return <Animated.View style={[styles.box, animatedStyle]} />;
}

Concept 3: withTiming and withSpring

Animate values smoothly:

import { withTiming, withSpring } from 'react-native-reanimated';
 
// Timing animation (linear/eased)
opacity.value = withTiming(1, { duration: 500 });
 
// Spring animation (bouncy)
translateX.value = withSpring(100, {
  damping: 10,
  stiffness: 100,
});

Example: Fade In Component

import { useEffect } from 'react';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
} from 'react-native-reanimated';
 
function FadeIn({ children }) {
  const opacity = useSharedValue(0);
 
  useEffect(() => {
    opacity.value = withTiming(1, { duration: 600 });
  }, []);
 
  const style = useAnimatedStyle(() => ({
    opacity: opacity.value,
  }));
 
  return <Animated.View style={style}>{children}</Animated.View>;
}

Example: Press Animation

import { Pressable } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';
 
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
 
function ScaleButton({ onPress, children }) {
  const scale = useSharedValue(1);
 
  const style = useAnimatedStyle(() => ({
    transform: [{ scale: scale.value }],
  }));
 
  return (
    <AnimatedPressable
      style={style}
      onPressIn={() => {
        scale.value = withSpring(0.95);
      }}
      onPressOut={() => {
        scale.value = withSpring(1);
      }}
      onPress={onPress}
    >
      {children}
    </AnimatedPressable>
  );
}

Example: Slide In List Items

import { useEffect } from 'react';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  withDelay,
} from 'react-native-reanimated';
 
function ListItem({ index, children }) {
  const translateY = useSharedValue(50);
  const opacity = useSharedValue(0);
 
  useEffect(() => {
    const delay = index * 100; // Stagger effect
 
    translateY.value = withDelay(delay, withTiming(0, { duration: 400 }));
    opacity.value = withDelay(delay, withTiming(1, { duration: 400 }));
  }, []);
 
  const style = useAnimatedStyle(() => ({
    transform: [{ translateY: translateY.value }],
    opacity: opacity.value,
  }));
 
  return <Animated.View style={style}>{children}</Animated.View>;
}

Example: Toggle Switch

import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withTiming,
  interpolateColor,
} from 'react-native-reanimated';
 
function Toggle({ value, onToggle }) {
  const translateX = useSharedValue(value ? 22 : 2);
  const progress = useSharedValue(value ? 1 : 0);
 
  useEffect(() => {
    translateX.value = withTiming(value ? 22 : 2);
    progress.value = withTiming(value ? 1 : 0);
  }, [value]);
 
  const trackStyle = useAnimatedStyle(() => ({
    backgroundColor: interpolateColor(
      progress.value,
      [0, 1],
      ['#ccc', '#4CAF50']
    ),
  }));
 
  const thumbStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: translateX.value }],
  }));
 
  return (
    <Pressable onPress={onToggle}>
      <Animated.View style={[styles.track, trackStyle]}>
        <Animated.View style={[styles.thumb, thumbStyle]} />
      </Animated.View>
    </Pressable>
  );
}
 
const styles = StyleSheet.create({
  track: {
    width: 50,
    height: 28,
    borderRadius: 14,
    justifyContent: 'center',
  },
  thumb: {
    width: 24,
    height: 24,
    borderRadius: 12,
    backgroundColor: 'white',
    position: 'absolute',
  },
});

Key Hooks to Know

HookPurpose
useSharedValueCreate animated value
useAnimatedStyleConvert value to style
useDerivedValueCompute value from other values
useAnimatedPropsAnimate non-style props (like SVG)

Key Functions

FunctionPurpose
withTimingEased animation
withSpringSpring physics animation
withDelayDelay before animation
withSequenceRun animations in order
withRepeatLoop animations

Common Mistake

Don't read .value inside useAnimatedStyle without the callback:

// Wrong - won't update
const style = { opacity: opacity.value };
 
// Right - reactive
const style = useAnimatedStyle(() => ({
  opacity: opacity.value,
}));

Reanimated takes some getting used to, but once it clicks, you'll never go back to the basic Animated API.

© 2026 Rahul Mandyal. All rights reserved.