Cole Redfearn Fullstack engineer, golfer, and explorer. Always curious!

Deep dive into React Native Reanimated

5 min read 1448

React Native Reanimated

React Native Reanimated is a powerful, intuitive library that allows you to create smooth animations and interactions for iOS and Android applications. Although there are many versatile and performant animation libraries for React Native, including the built-in Animated API, we’ll examine Reanimated in-depth to discover why it’s the superior choice in my opinion.

Let’s get started!

Reanimated code execution

Reanimated’s core strength lies in its ability to improve the performance and responsiveness of React Native applications, giving animations a smooth finish that can be achieved only through instantaneous code execution.

To understand how Reanimated works, we first need to understand how React Native executes code.

React Native threads

React Native has three separate threads that allow the execution of time-intensive JavaScript code without affecting the UI’s responsiveness. The UI thread is the native thread. It runs either Swift or Objective C for iOS and either Java or Kotlin for Android. The application’s UI is manipulated solely on the UI thread.

The JavaScript thread is responsible for the render logic, like which views are displayed and in what manner. It executes JavaScript separately via a JavaScript engine. Finally, the bridge enables asynchronous communication between the UI and the JavaScript thread. To avoid slowing down performance, it should only be used to transfer small amounts of data.

These interactions start on the JavaScript thread and must be reflected on the main thread, potentially causing problems when dealing with event-driven interactions.

Reanimated vs. the Animated API

Because communication between the UI and the JavaScript thread is asynchronous, the Animated API delays updates by at least one frame, which lasts approximately 16.67ms. The delay can last even longer when the JavaScript thread is also executing other processes like running React diffing and processing network requests.

Known as dropping a frame, these delays can harm your UX and make animations appear clunky. Reanimated solves this problem by removing animation and event handling logic from the JavaScript thread and running it directly on the UI thread.

Additionally, Reanimated defines worklets, which are tiny chunks of JavaScript code that can be moved to a separate JavaScript virtual machine and executed synchronously on the UI thread. Worklets cause animations to run as soon as they are triggered, creating a more satisfying UX.

We made a custom demo for .
No really. Click here to check it out.

Shared Values

Shared Values, which are similar to stateful data in a regular React application, store dynamic data that you can animate in Reanimated. However, instead of sharing data between components like you would with stateful data, Shared Values share data between the UI thread and the JavaScript thread.

When data inside of these Share Values is updated, the Reanimated library registers the change and executes an animation, similar to how React re-renders a component when state is updated.

Creating a Shared Value looks quite similar to creating a piece of state with the useState Hook; simply use the useSharedValue Hook instead.

Let’s say we want to create a box that changes height when a button is pressed. We’ll define a boxHeight variable as a Shared Value so that we can share it between the UI thread and the JavaScript thread during the animation:

const boxHeight = useSharedValue(150);

In the example above, we created a Shared Value called boxHeight and set its initial value to 150. To access the boxHeight value later in your code, you must reference boxHeight.value instead of just boxHeight.

The useSharedValue Hook returns an object, and the initial value is saved to the .value property of the object. To update the Shared Value, which is typically done after some predetermined user input, simply update the .value property within a function. Reanimated will register the change:

function toggleHeight() {
  boxHeight.value === 450 ?
  boxHeight.value = 150 :
  boxHeight.value = 450;
}

Now, let’s explore how we can use these Shared Values in Reanimated’s worklets.

Reanimated worklets

Worklets are simple functions that allow us to execute JavaScript code synchronously on the UI thread. Typically, worklets return a React component’s style property. A worklet is triggered by any change to the Shared Value it references.

To declare a worklet, we can use the worklet directive in the beginning of the function definition. In the code block below, we are declaring the boxAnimation function using the worklet directive, indicating that the function is a worklet. We are returning an updated height property using the Shared Value boxHeight:

const boxAnimation = () => {
  'worklet';
  return {
    height: withTiming(boxHeight.value, {duration: 750})
  };
};

More commonly, we can use the useAnimatedStyle Hook to declare a worklet, which allows us to pass in a callback function as an argument. Now, the callback function will be treated as a worklet:

const boxAnimation = useAnimatedStyle(() => {
  return {
    height: withTiming(boxHeight.value, {duration: 750})
  };
});

Of the two methods for declaring a worklet, I recommend using the useAnimatedStyle Hook. To use the worklet directive method, we must add a secondary function that calls the runOnUI method and passes the boxAnimation function as a parameter. On the other hand, the useAnimatedStyle hook abstracts that secondary function away, automatically running the callback passed to the useAnimatedStyle Hook on the UI thread.

In both of our examples, whenever the value of boxHeight.value is updated, the worklet will trigger an animation showing the box either expanding or contracting vertically.

Utility methods

withTiming

In our examples above, we used the withTiming utility method, which allows us to create a simple animation that gradually transitions from its start point to its endpoint, giving us the ability to control the duration and acceleration of the transition. 

withTiming takes two parameters. The first, which is required, is the Shared Value that will update. In our case, it is boxHeight.

The second optional parameter is an object with two properties. duration controls the length of time the animation takes, and easing controls the animation’s acceleration and deceleration. The default values are 300ms for duration and in-out quad easing for easing.

withSpring

The withSpring method is similar to withTiming, however, it creates a different animation effect in which the element passes its endpoint before settling into its ending position.

withSpring only has one required parameter, which is the Shared Value to be updated. It also has six optional parameters listed below. However, the default values will suffice for most use cases:

  • damping: 10
  • mass: 1
  • stiffness: 100
  • overshootClamping: false
  • restDisplacementThreshold: 0.01
  • restSpeedThreshold: 2

useAnimatedStyle Hook

In our previous example, the useAnimatedStyle Hook creates a worklet that links the Shared Value boxHeight with a component that uses boxHeight.value in its style properties. When giving React components properties, we must use the component version that we can animate.

For example, instead of using a <View /> tag, we should use an <Animated.View /> tag. All children of <Animated.View /> will be subject to the animation applied to the parent.

When setting styles for the component, be sure to pass the styles as an array. The first element is an object containing all of the styles you would typically use, including height. The second element is the worklet that was defined earlier.

Now, let’s combine all of the Reanimated tools that we’ve covered to create a simple grey box that expands and contracts when we press a button:

// import statements where we add the functionality to use hooks and methods from Reanimated
import React from 'react';
import { View, Button } from 'react-native';
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated';
export default function ExpandingTextBox() {
  // creating shared value via useSharedValue
  const boxHeight = useSharedValue(150);
  // creating worklet via useAnimatedStyle, and incorporating the withTiming method
  const boxAnimation = useAnimatedStyle(() => {
    return {
      height: withTiming(boxHeight.value, {duration: 750})
    }
  });
  // function that toggles the value of boxHeight so it can expand and contract
  function toggleHeight() {
    boxHeight.value === 450 ?
    boxHeight.value = 150 :
    boxHeight.value = 450;
  };
  // styles to use on our grey box
  const styles = {
    box: {
      width: 400,
      height: 150,
      backgroundColor: 'grey',
      borderRadius: 15,
      margin: 100,
      padding: 20,
      display: 'flex'
    }
  };
  return (
    <View style={styles.app}>
      {/* Animated.View component, with the typical styles contained in styles.box,
and the worklet "boxHeight" that updates the height property alongside it */}
      <Animated.View style={[styles.box, boxAnimation]}>
        {/* button that fires off toggleHeight() function every time it's pressed */}
        <Button title='More' onPress={() => toggleHeight()} />
      </Animated.View>
    </View>
  )
};

The output of the code above will look like the animation below:

Reanimated Gif Dropdown

Let’s add another Shared Value and worklet to control text opacity. We’ll also add some more detail to the styling.

Now, we have a fully functional animated text dropdown!

Reanimated Gif Styling

Conclusion

Reanimated offloads event-driven interactions from the JavaScript thread and instead executes them synchronously on the UI thread. With Reanimated, you no longer have to worry about dropping frames or limiting the workload of your JavaScript thread.

For more information on Reanimated’s methods, hooks, and animations, I recommend checking out the Reanimated documentation.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Cole Redfearn Fullstack engineer, golfer, and explorer. Always curious!

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

One Reply to “Deep dive into React Native Reanimated”

Leave a Reply