The Widlarz Group Blog
Reanimated 2 - the new approach to creating animations in React Native
August 27, 2020
- Introduction
- Project Setup
- useSharedValue
- useAnimatedGestureHandler
- useAnimatedStyle
- Quick fix and “worklet” directive
- useDerivedValue and useAnimatedProp
- Adding onDragComplete handler and runOnJS method
- Custom hook - useSlider
- Animate Slider2 with useSlider hook
- Interpolation
- withTiming and Easing functions
- Conclusions
Introduction
Recently, I’ve got a chance to work with Reanimated 2 library. The new version is completely different than v1 as it exposes React hook based API. This guide uses 2.5.0 version and has been recently updated. We promise to keep updating each time a new version is released.
I have to admit, that working with the new API was quite a pleasure, firstly the new hooks set is really intuitive for React developers, secondly, animations are smooth and performant. I encourage you to start using Reanimated 2 in your React Native projects.
In this article, we are not going to focus so much on theory. If you are looking for that kind of knowledge, I refer you to the great documentation. Our aim is to take a look at Reanimated 2 hooks and learn how to use them in code. We are going to build practical examples of animated sliders using the new Reanimated 2 API and our custom useSlider hook.
You don’t need to be familiar with the previous version to make animations with Reanimated 2!
Below you can see a preview of what we will build together, step by step.

At the end of each step, there’s a link to the current code (in case you’ve got stuck).
Let’s start then!
Project Setup
There is a starter repo on Github for this tutorial. We start by cloning the repo
git clone -b starter git@github.com:TheWidlarzGroup/reanimated2-slider-article.git
then install packages
cd reanimated2-slider-article && yarn
and if you develop on iOS simulator:
cd ios && pod install
Intro
The starter project consists of 3 sliders. Each slider is built with 3 fundamental View components.
<View style={styles.slider}>
<View style={styles.progress} />
<View style={styles.knob} />
</View>
slider
- wrapperprogress
- absolute positioned View, for displaying slider progressknob
- draggable element
Our first task is to animate Slider1
using Reanimated 2 hooks. Then we will build custom hook useSlider
with reusable logic.
Let’s start our project
yarn ios
// or
yarn android
And move to Slider1.js
file
useSharedValue
Probably you are familiar with Animated.Value
concept.
When it comes to Reanimated 2 we use the useSharedValue hook to keep animation ‘state’. Shared means that the value is accessible across 2 threads: UI and JS Thread.
In our example we keep two values in ‘state’:
translateX
- a distance from beginning to the end of the slider.isSliding
- a boolean which tells us whether we are currently sliding or not.
import { useSharedValue } from 'react-native-reanimated'
//...
const translateX = useSharedValue(0)
const isSliding = useSharedValue(false)
Our animations are based on changing shared values.
What’s important to mention, to change shared values we use .value
syntax:
For instance:
isSliding.value = true
// or
translateX.value = 100
useAnimatedGestureHandler
Reanimated 2 provides us with a hook useAnimatedGestureHandler to deal with Gesture Handlers easily. The hook accepts an object where we can configure events like:
onStart, onActive, onEnd, onCancel, onFail, onFinish
Each event has access to 2 parameters:
-
gesture event
- an object that consists of information like translate, velocity, current position, and more, dependent on Gesture Handler we use. -
context
- a plain JS object to store data between events. We can keep in context any information we want.
In return from hook, we get an object, which needs to be passed to Gesture Handler (onGestureEvent).
In our example, we use PanGestureHandler
as a wrapper for Knob to make it draggable across the slider.
// import {useSharedValue} from 'react-native-reanimated'
import Animated, {useSharedValue, useAnimatedGestureHandler} from 'react-native-reanimated'
import {PanGestureHandler} from 'react-native-gesture-handler'
//... configuring PanGestureHandler here
const onGestureEvent = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.offsetX = translateX.value
},
onActive: (event, ctx) => {
isSliding.value = true
translateX.value = event.translationX + ctx.offsetX
},
onEnd: () => {
isSliding.value = false
},
})
//... Wrapping our Knob with PanGestureHandler
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View style={styles.knob}/>
</PanGestureHandler>
useAnimatedStyle
The next and one of the most important hook is useAnimatedStyle one.
Inside this hook, we define style properties we want to be animated. Then we need to pass whatever hook has returned to an Animated
component as a style property.
In our case, we created two animated styles.
scrollTranslationStyle
- to show the Knob translationprogressStyle
- to animate progress bar when the knob is being dragged
We also tell RN, that our views are Animated.
// import Animated, {useSharedValue, useAnimatedGestureHandler} from 'react-native-reanimated'
import Animated, {
useSharedValue,
useAnimatedGestureHandler,
useAnimatedStyle,
} from 'react-native-reanimated'
const scrollTranslationStyle = useAnimatedStyle(() => {
return { transform: [{ translateX: translateX.value }] }
})
const progressStyle = useAnimatedStyle(() => {
return {
width: translateX.value + KNOB_WIDTH,
}
})
return (
<View style={styles.slider}>
{/* <View style={styles.progress} /> */}
<Animated.View style={[styles.progress, progressStyle]} />
<PanGestureHandler onGestureEvent={onGestureEvent}>
{/* <Animated.View style={styles.knob} /> */}
<Animated.View style={[styles.knob, scrollTranslationStyle]} />
</PanGestureHandler>
</View>
)
If you’ve followed to this point, you should be able to move the Knob.

current code here.
Quick fix and “worklet” directive
There is a problem with our slider. We can move the knob beyond our slider. Let’s fix that by adding the clamp
function, that would not allow the knob to leave defined bounds.
//...
onActive: (event, ctx) => {
const clamp = (value, lowerBound, upperBound) => {
return Math.min(Math.max(lowerBound, value), upperBound)
}
isSliding.value = true
//translateX.value = event.translationX + ctx.offsetX
translateX.value = clamp(
event.translationX + ctx.offsetX,
0,
SLIDER_WIDTH - KNOB_WIDTH
)
}
Indeed, there is no need to define a clamp
function inside the useAnimatedGestureHandler. For cleanliness, we move function definition to a separate file utils.js
. Also, don’t forget to import it in our Slider1.js
file.
// utils.js
export const clamp = (value, lowerBound, upperBound) => {
'worklet'
return Math.min(Math.max(lowerBound, value), upperBound)
}
Perhaps you have noticed that the new string appeared - "worklet"
.
There is no need to write "worklet"
directive inside reanimated 2 hooks. Each of reanimated 2 hooks, uses worklet directive under the hood.
If we want to run Javascript functions on the UI thread, we need to place "worklet"
directive on the top of a function definition. (similar to our clamp method)
Link to current code
useDerivedValue and useAnimatedProp
useDerivedValue - we use this hook, to calculate a new value, based on changing shared value. In our example we will calculate step
and then return its value as a string, to animate it in AnimatedText component.
//import Animated, {useSharedValue, useAnimatedGestureHandler, useAnimatedStyle } from 'react-native-reanimated'
import Animated, {
useSharedValue,
useAnimatedGestureHandler,
useAnimatedStyle,
useDerivedValue,
} from 'react-native-reanimated'
const MAX_RANGE = 20
//...
const stepText = useDerivedValue(() => {
const sliderRange = SLIDER_WIDTH - KNOB_WIDTH
const oneStepValue = sliderRange / MAX_RANGE
const step = Math.ceil(translateX.value / oneStepValue)
return String(step)
})
then create a new file AnimatedText.js
useAnimatedProp - is similar to useAnimatedStyle. The difference is that we use useAnimatedProp when we want to animate properties, which are not style properties (background, opacity, etc.)
//AnimatedText.js
import * as React from 'react'
import Animated, { useAnimatedProps } from 'react-native-reanimated'
import { TextInput } from 'react-native-gesture-handler'
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
const AnimatedText = ({ text }) => {
const animatedProps = useAnimatedProps(() => {
return {
text: text.value,
}
})
return (
<AnimatedTextInput
underlineColorAndroid="transparent"
editable={false}
value={text.value}
animatedProps={animatedProps}
/>
)
}
export default AnimatedText
and finally in our Slider1.js
import AnimatedText from './AnimatedText'
//...
// <Animated.View style={[styles.knob, scrollTranslationStyle]} />
<Animated.View style={[styles.knob, scrollTranslationStyle]}>
<AnimatedText text={stepText} />
</Animated.View>

You can find the code for slider with animated text here.
Adding onDragComplete handler and runOnJS method
Often, we want to add some feedback when the knob is being dragged to the end of the slider. With Reanimated 2 it’s quite simple to implement. Let’s assume that dragging is completed just after we have dropped the knob, and the distance from the knob to the max bound is less than 3 pt.
We call the onDragSuccess function on the UI thread when we’ve finished dragging the knob. In that case, we need to wrap our function with the runOnJS
method. If we forget about wrapping our Javascript callback, Reanimated 2 will inform us about that fact by displaying an appropriate error. You can read more about runOnJS
here.
// import {StyleSheet, View} from 'react-native'
import { StyleSheet, View, Alert } from 'react-native'
// import Animated, { useSharedValue, useAnimatedGestureHandler, useAnimatedStyle, useDerivedValue } from 'react-native-reanimated'
import Animated, {
useSharedValue,
useAnimatedGestureHandler,
useAnimatedStyle,
useDerivedValue,
runOnJS,
} from 'react-native-reanimated'
const onDraggedSuccess = () => {
Alert.alert('dragged')
}
//...
onEnd: () => {
isSliding.value = false
if (translateX.value > SLIDER_WIDTH - KNOB_WIDTH - 3) {
runOnJS(onDraggedSuccess)()
}
}

Code with added onDraggedSuccess here
Custom hook - useSlider
So far, we have built a slider animation using Reanimated 2 hooks.
As the React world loves hooks, we will move a step forward and create custom hook - useSlider
. We will use this useSlider hook to animate our second slider. That would be a good test for how our shared values behave when we passed them across components.
Let’s create a new file for our hook useSlider.js
. We will take our animation logic from Slider1
. To increase the reusability, we will be passing following arguments to our hook:
sliderWidth
(number)knobWidth
(number),onDraggedSuccess
(callback).maxRange
(number) - to track a current step, optionalinitialValue
(number) - if we want to set the initial position of knob inside a slider, optional
The useSlider hook returns:
onGestureEvent
- values (
translateX
,isSliding
,stepText
) - styles (
scrollTranslationStyle
,progressStyle
)
// useSlider.js
import {
useSharedValue,
useAnimatedGestureHandler,
useAnimatedStyle,
useDerivedValue,
runOnJS,
} from 'react-native-reanimated'
import { clamp } from './utils'
export const useSlider = (
sliderWidth,
knobWidth,
onDraggedSuccess,
maxRange = 10,
initialValue = 0
) => {
const SLIDER_RANGE = sliderWidth - knobWidth
const STEP = SLIDER_RANGE / maxRange ?? 1
const translateX = useSharedValue(STEP * initialValue)
const isSliding = useSharedValue(false)
const onGestureEvent = useAnimatedGestureHandler({
onStart: (_, ctx) => {
ctx.offsetX = translateX.value
},
onActive: (event, ctx) => {
isSliding.value = true
translateX.value = clamp(
event.translationX + ctx.offsetX,
0,
SLIDER_RANGE
)
},
onEnd: () => {
isSliding.value = false
if (translateX.value > SLIDER_RANGE - 3) {
runOnJS(onDraggedSuccess)()
}
},
})
const scrollTranslationStyle = useAnimatedStyle(() => {
return { transform: [{ translateX: translateX.value }] }
})
const progressStyle = useAnimatedStyle(() => {
return {
width: translateX.value + knobWidth,
}
})
const stepText = useDerivedValue(() => {
const step = Math.ceil(translateX.value / STEP)
return String(step)
})
return {
onGestureEvent,
values: {
isSliding,
translateX,
stepText,
},
styles: {
scrollTranslationStyle,
progressStyle,
},
}
}
Animate Slider2 with useSlider hook
To show a more interesting example, we will animate Slider2
with our useSlider hook. The idea of this example comes from a slider, which already has been animated with Reanimated 1, by William Candilon in this video
Firstly, uncomment in App.js these lines:
import Slider2 from './Slider2';
<View style={{margin: 50}} />
<Slider2 />
then let’s use our custom hook in Slider2.js
// import {StyleSheet, View, Text} from 'react-native'
import { StyleSheet, View, Alert } from 'react-native'
import Animated from 'react-native-reanimated'
import { PanGestureHandler } from 'react-native-gesture-handler'
import { useSlider } from './useSlider'
import AnimatedText from './AnimatedText'
// ...
const Slider2 = () => {
const onDragCompleteHandler = () => {
Alert.alert(stepText.value, String(translateX.value))
}
const {
onGestureEvent,
values: { translateX, isSliding, stepText },
styles: { scrollTranslationStyle, progressStyle },
} = useSlider(SLIDER_WIDTH, KNOB_WIDTH, onDragCompleteHandler, STEP)
return (
<>
<View style={styles.slider}>
<Animated.View style={[styles.progress, progressStyle]} />
<PanGestureHandler onGestureEvent={onGestureEvent}>
<Animated.View style={[styles.knobContainer, scrollTranslationStyle]}>
<Knob isSliding={isSliding} />
</Animated.View>
</PanGestureHandler>
</View>
<View style={{ marginTop: 40 }}>
<AnimatedText text={stepText} />
</View>
</>
)
}
Also in Knob.js
we animate the opacity of two images:
- penguin with open eyes (
assets/up.png
) - penguin with closed eyes (
assets/down.png
)
based on isSliding.value
which can be true or false.
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
const Knob = ({ isSliding }) => {
const knobUpStyle = useAnimatedStyle(() => {
return {
opacity: isSliding.value ? 1 : 0,
}
})
const knobDownStyle = useAnimatedStyle(() => {
return {
opacity: isSliding.value ? 0 : 1,
}
})
return (
<View style={styles.container}>
{/* <Image source={require("./assets/up.png")} style={styles.image} /> */}
<Animated.Image
source={require('./assets/up.png')}
style={[styles.image, knobUpStyle]}
/>
{/* <Image source={require("./assets/down.png")} style={styles.image} /> */}
<Animated.Image
source={require('./assets/down.png')}
style={[styles.image, knobDownStyle]}
/>
</View>
)
}
If you’ve done everything correctly, you could be able to see that animation

Code from this step
Interpolation
In this part, we will add some rotation styling to our penguin and interpolate color of the progress bar. To achieve this we use the interpolate
method from the Reanimated 2 library.
Interpolation helps you to create good animations by mapping input ranges to output ranges. We use it like this:
// Example
const value = interpolate(
sharedValue, // e.g translateX.value
inputRange, // e.g : [0, 100]
outputRange, // e.g : [360, 1000]
extrapolation // Clamp, Extend, Identity
)
In Slider2.js
let’s add
// import Animated from 'react-native-reanimated'
import Animated, {useAnimatedStyle, interpolate, Extrapolate} from 'react-native-reanimated'
// ..
// below useSlider hook
const rotateStyle = useAnimatedStyle(() => {
const rotate = interpolate(
translateX.value,
[0, SLIDER_RANGE], // between the beginning and end of the slider
[0, 4 * 360], // penguin will make 4 full spins
Extrapolate.CLAMP,
)
return {
transform: [{rotate: `${rotate}deg`}],
}
})
//..
// <Knob isSliding={isSliding} />
<Knob isSliding={isSliding} rotateStyle={rotateStyle} />
and in Knob.js
// const Knob = ({isSliding}) => {
const Knob = ({isSliding, rotateStyle}) => {
// <View style={styles.container}>
<Animated.View style={[styles.container, rotateStyle]}>
{/* ... */}
</Animated.View>
Color Interpolation
There is a handy way in Reaniamted2 to interpolate colors based on shared value changes. For that purpose, we use the interpolateColor
method. Its way of working is similar to the interpolation method, except that this time our output array consists of colors. The creators of the library made sure that it is possible to pass colors in the most popular formats. Let’s use RGB format here.
// Slider2.js
// import Animated, {useAnimatedStyle, interpolate, Extrapolate } from 'react-native-reanimated'
import Animated, {useAnimatedStyle, interpolate, Extrapolate, interpolateColor} from 'react-native-reanimated'
//..
const backgroundStyle = useAnimatedStyle(() => {
const backgroundColor = interpolateColor(
translateX.value,
[0, SLIDER_RANGE],
['rgb(129,212,250)', 'rgb(3,169,244)'],
);
return {
backgroundColor,
};
});
//...
// <Animated.View style={[styles.progress, progressStyle]}/>
<Animated.View style={[styles.progress, progressStyle, backgroundStyle]}/>
At the end of this section, we are able to see the penguin rotation and the progress background color animation.

Code can be found here
withTiming and Easing functions
This is the last part of the article. We will make a small experiment by moving our penguin programmatically by changing trasnlateX.value
, in the button onPress method.
Additionally, we will use timing functions from Reanimated 2.
Let’s take a look, how we can animate shared values by the withTiming
method.
// Example usage of withTiming
// withTiming takes 3 arguments
// 1. new target value
// 2. animation config object
// 3. callback function, which will be executed right after the animation is complete
translate.value = withTiming(
300,
{
duration: 1000,
easing: Easing.linear,
},
() => {
// do sth
},
);
And in our project add these lines:
// Slider2.js
// import {StyleSheet, View, Alert} from 'react-native'
import { StyleSheet, View, Alert, Button } from 'react-native'
// import Animated, {useAnimatedStyle, interpolate, Extrapolate, interpolateColor } from 'react-native-reanimated'
import Animated, {
useAnimatedStyle,
interpolate,
Extrapolate,
interpolateColor,
withTiming,
Easing,
} from 'react-native-reanimated'
//... in return statement, below Animated Text let's add 2 Buttons
<View>
<Button
title='Slide to beginning'
onPress={() => {
isSliding.value = true
translateX.value = withTiming(
0,
{
duration: 3000,
easing: Easing.bounce,
},
() => {
isSliding.value = false
},
)
}}
/>
<Button
title='Slide to end'
onPress={() => {
isSliding.value = true
translateX.value = withTiming(
SLIDER_RANGE,
{
duration: 1000,
easing: Easing.linear,
},
() => {
isSliding.value = false
},
)
}}
/>
</View>
Now, you can see two extra buttons, and move the slider by clicking them, like on the video below.

You can find the whole code here.
Reanimated 2 offers us more methods to create interesting animations like withSpring, withDelay, withRepeat, cancelAnimation
, and more. I encourage you to give it a try. We have left a Slider3.js
file, with a static slider. Feel free to make your experiment using Reanimated 2 API. Hope you guys have fun using it.
Conclusions
To start writing code with the new Reanimated 2 API, you don’t need to have prior experience with react-native-reanimated library. The library offers us the powerful API to create eye-catching and effective animations.
The first version of this article was created in August 2020 and used the alpha version. Since then, the library has been in the intensive development phase all the time. The developers have made every effort to ensure that the API was stable and did not cause errors. On the day of writing the article update, the library has a stable version (2.5.0) that can be safely used in production apps.
In my opinion, Reanimated2 will be setting new trends and will be the first choice by React Native developers when it comes to choosing a library for building animations.
By reading this article, you’ve gained some practical knowledge about how you can use the new Reanimated 2 API. I hope you’ve enjoyed reading it. For more examples, I refer you to the inspiring YT channel

Written by Bartek Bajda.