Updated April 21, 2021

How to use Reanimated 2 (a beginners guide)

Reanimated 2 is a hyper powerful library that all the cool kids use but has always been a bit of a blackbox to me... I saw the power of it but was overwhelmed by the API.

Today I want to share with you what I've learned and we'll create our first Reanimated 2 animation together - an image that changes sizes.

Let's start with an app that renders a 100x100 image. All standard React Native.

import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';

export default () => {
  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column',
        backgroundColor: '#7CA1B4',
      }}
    >
      <TouchableOpacity
        onPress={() => {
          alert('todo!');
        }}
      >
        <Image
          source={require('./rns.png')}
          resizeMode="contain"
          style={{ width: 100, height: 100 }}
        />
      </TouchableOpacity>
    </View>
  );
};

01

We'll be animating the size of the image. That means that we need to store the value to be animated somewhere.

Reanimated 2 has a concept of shared values. These are values that can be shared across different threads (think JavaScript thread and UI thread) thus the name of shared value.

It's where you'll want to store your "animateable" data - in this case the random number that we'll use to drive the size of the image.

useSharedValue returns a value - similar to using .current on a useRef hook - that means that we can't just access the result directly. We need to access the .value property of the returned data.

Also note in the following code that I've replaced the Image component with an Animated.Image. This is in preparation for when the randomNumber value will change.

import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, { useSharedValue } from 'react-native-reanimated';

export default () => {
  const randomNumber = useSharedValue(100);

  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column',
        backgroundColor: '#7CA1B4',
      }}
    >
      <TouchableOpacity
        onPress={() => {
          alert('todo!');
        }}
      >
        <Animated.Image
          source={require('./rns.png')}
          resizeMode="contain"
          style={{ width: randomNumber.value, height: randomNumber.value }}
        />
      </TouchableOpacity>
    </View>
  );
};

Now let's go ahead and change that randomNumber. Make sure you use .value when accessingly the data.

import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, { useSharedValue } from 'react-native-reanimated';

export default () => {
  const randomNumber = useSharedValue(100);

  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column',
        backgroundColor: '#7CA1B4',
      }}
    >
      <TouchableOpacity
        onPress={() => {
          randomNumber.value = Math.random() * 350;
        }}
      >
        <Animated.Image
          source={require('./rns.png')}
          resizeMode="contain"
          style={{ width: randomNumber.value, height: randomNumber.value }}
        />
      </TouchableOpacity>
    </View>
  );
};

But when we press the image nothing is happening... why?

It's not changing because, unlike when calling setState, it doesn't cause a re-render to happen (which is good) we're just changing a value.

What we need to do is spawn a worklet that can adjust to changes in randomNumber.

This worklet will run on the UI thread (rather than the JavaScript thread) allowing us to see a smooth native animation.

To spawn this worklet we're going to use the useAnimatedStyle hook. This hook will return a style object that updates with randomNumber. This style object can then be applied to our Animated.Image.

useAnimateStyle is similar to useEffect but actually returns a value (the style object).

import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
} from 'react-native-reanimated';

export default () => {
  const randomNumber = useSharedValue(100);

  const style = useAnimatedStyle(() => {
    return { width: randomNumber.value, height: randomNumber.value };
  });

  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column',
        backgroundColor: '#7CA1B4',
      }}
    >
      <TouchableOpacity
        onPress={() => {
          randomNumber.value = Math.random() * 350;
        }}
      >
        <Animated.Image
          source={require('./rns.png')}
          resizeMode="contain"
          style={style}
        />
      </TouchableOpacity>
    </View>
  );
};

Now when we press the image the size of it changes (granted it's not animated - we'll get to that).

https://media.giphy.com/media/GjKW16FwHfXOtkadLy/giphy.gif

The final piece of (this) puzzle is to actually animate the values. We've created all the necessary relationships between data and have things updating where they need to be - now we need to choose the animation to use.

Today we'll make "springy".

By importing withSpring we can tell Reanimated 2 how we want that data to change.

import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

export default () => {
  const randomNumber = useSharedValue(100);

  const style = useAnimatedStyle(() => {
    return {
      width: randomNumber.value,
      height: randomNumber.value,
    };
  });

  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column',
        backgroundColor: '#7CA1B4',
      }}
    >
      <TouchableOpacity
        onPress={() => {
          randomNumber.value = withSpring(Math.random() * 350);
        }}
      >
        <Animated.Image
          source={require('./rns.png')}
          resizeMode="contain"
          style={style}
        />
      </TouchableOpacity>
    </View>
  );
};
https://media.giphy.com/media/RSukX8m2ogtoKF0NWO/giphy.gif

Alternatively we could use withSpring inside of useAnimatedStyle to customize how each property changes.

import React from 'react';
import { View, TouchableOpacity, Image } from 'react-native';
import Animated, {
  useSharedValue,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

export default () => {
  const randomNumber = useSharedValue(100);

  const style = useAnimatedStyle(() => {
    return {
      width: withSpring(randomNumber.value),
      height: withSpring(randomNumber.value, { stiffness: 10 }),
    };
  });

  return (
    <View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'column',
        backgroundColor: '#7CA1B4',
      }}
    >
      <TouchableOpacity
        onPress={() => {
          randomNumber.value = Math.random() * 350;
        }}
      >
        <Animated.Image
          source={require('./rns.png')}
          resizeMode="contain"
          style={style}
        />
      </TouchableOpacity>
    </View>
  );
};
https://media.giphy.com/media/9cJXjCvcDq41WKRvKz/giphy.gif

And that's on me trying to figure out the moving parts of Reanimated 2. To me it's a pretty complex library to first learn but the power of it is incredible.

If you have any comments/corrections/questions please reach out on Twitter.

React Native School Logo

React Native School

Want to further level up as a React Native developer? Join React Native School! You'll get access to all of our courses and our private Slack community.

Learn More