---
title: "Running React Native dependent animations on UI thread using Reanimated"
description:
  "Learn how to run dependent animations on the UI thread to optimize and
  improve the performance with React Native Reanimated library"
canonical_url: "https://www.bigbinary.com/blog/running-react-native-dependent-animations-on-ui-thread-using-reanimated"
markdown_url: "https://www.bigbinary.com/blog/running-react-native-dependent-animations-on-ui-thread-using-reanimated.md"
---

# Running React Native dependent animations on UI thread using Reanimated

Learn how to run dependent animations on the UI thread to optimize and improve
the performance with React Native Reanimated library

- Author: Sangamesh Somawar
- Published: March 13, 2023
- Categories: React Native

![Slider](https://user-images.githubusercontent.com/93119254/222339128-047c3afe-543f-48a0-9420-a8c6969a156c.gif)

Here we have a slider. When the user slides the slider, the loader needs to show
how much is loaded. We want to animate the loader component when the slider
moves. In other words, the loader animation is "dependent" on the slider
animation.

This is an example of _Dependent Animations_ on the UI thread. _Dependent
Animation_ is when one view is animated based on another element.

first we need a gesture handler to detect the slider events. Then, based on the
slider events we need to animate the progress ofthe loader component.

### Building gesture handler to detect the slider events

```jsx
const [width, setWidth] = useState(200);
const x = useSharedValue(0);

const gestureHandler = useAnimatedGestureHandler({
  onStart: (event, ctx) => {
    x.value = event.absoluteX;
  },
  onActive: (event, ctx) => {
    x.value = event.absoluteX;
    // We need to calculate `progress` here based on the slider position.
  },
});

const animatedStyle = useAnimatedStyle(() => {
  return {
    transform: [
      {
        translateX: x.value,
      },
    ],
  };
});

return (
  <PanGestureHandler onGestureEvent={gestureHandler}>
    <Animated.View style={[{ height: 20 }]}>
      <Animated.View
        pointerEvents="none"
        style={[
          {
            backgroundColor: "blue",
            height: 20,
            width: 20,
            borderRadius: 10,
          },
          animatedStyle,
        ]}
      />
      <View
        pointerEvents="none"
        style={{
          backgroundColor: "black",
          height: 2,
          width: "100%",
          position: "absolute",
          top: 10,
        }}
      />
    </Animated.View>
  </PanGestureHandler>
);
```

**Loader component:**

```jsx
<View style={{ height: 20, marginTop: 10 }}>
  <Lottie
    style={{
      alignSelf: "center",
      width: "100%",
    }}
    progress={"Calculated `progress` based on slider position."}
    source={require("./progress")}
    autoPlay={false}
    loop={false}
  />
</View>
```

### Animate the loader based on the slider position

Before we move further, let's understand the difference between UI Thread and JS
Thread. The UI Thread handles rendering and gestures of Android and iOS views,
whereas the JS Thread takes care of all the logic of the React Native
application.

<video width="320" height="240" controls muted>
  <source src="https://user-images.githubusercontent.com/93119254/199667917-fa5e172e-6b42-481a-abb4-34040179cef7.mov">
</video>

We have two approaches for animating the loader.

In the first approach, when the slider moves, we can store the `progress` in
react state and pass it to [Lottie animation](https://lottiefiles.com/). With
this approach, the entire component rerenders on setting the `progress`.

```jsx {1,8,17}
const [progress, setProgress] = useState(0);
const gestureHandler = useAnimatedGestureHandler({
  onStart: (event, ctx) => {
    x.value = event.absoluteX;
  },
  onActive: (event, ctx) => {
    x.value = event.absoluteX;
    runOnJS(setProgress)(x.value / width);
  },
});

<Lottie
  style={{
    alignSelf: "center",
    width: "100%",
  }}
  progress={progress}
  source={require("./progress")}
  autoPlay={false}
  loop={false}
/>;
```

In the second approach, when the slider moves, we can calculate `progress` and
pass it to the loader component via the
[`useAnimatedProps`](https://docs.swmansion.com/react-native-reanimated/docs/api/hooks/useAnimatedProps/).
In this way, the `progress` gets calculated on the UI thread itself. Hence it
avoids rerenders.

```jsx {3,18}
const LottieAnimated = Animated.createAnimatedComponent(Lottie);
const [progress, setProgress] = useState(0);
const lottieAnimatedProps = useAnimatedProps(() => progress: x.value / width);
const gestureHandler = useAnimatedGestureHandler({
  onStart: (event, ctx) => {
    x.value = event.absoluteX;
  },
  onActive: (event, ctx) => {
    x.value = event.absoluteX;
  },
});

<LottieAnimated
  style={{
    alignSelf: "center",
    width: "100%",
  }}
  animatedProps={lottieAnimatedProps}
  source={require("./progress")}
  autoPlay={false}
  loop={false}
/>;
```

### Conclusion

With the first approach, whenever the slider moves, the UI thread will pass the
gesture event to the JS thread to store the `progress` value in react state, and
when the `progress` value changes in the JS thread, it causes a re-render. This
approach creates a lot of traffic over
[Communication Bridge](https://reactnative.dev/architecture/threading-model)
because of the message exchange between UI and JS threads.

So we should prefer the second approach to run any calculations on the UI thread
instead of the JS thread. Here is the entire code for reference:

```jsx
let approach1ReRenderCount1 = 0;
const Approach1: () => Node = () => {
  const [width, setWidth] = useState(1);
  const x = useSharedValue(0);

  const [progress, setProgress] = useState(0);
  const gestureHandler = useAnimatedGestureHandler({
    onStart: (event, ctx) => {
      x.value = event.absoluteX;
    },
    onActive: (event, ctx) => {
      x.value = event.absoluteX;
      runOnJS(setProgress)(x.value / width);
    },
  });

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateX: x.value,
        },
      ],
    };
  });

  return (
    <SafeAreaView>
      <View
        style={{
          height: 150,
          paddingHorizontal: 20,
          borderWidth: 1,
          margin: 10,
          justifyContent: "center",
        }}
        onLayout={({
          nativeEvent: {
            layout: { width },
          },
        }) => {
          setWidth(width);
        }}
      >
        <Text style={{ fontSize: 20, paddingBottom: 10 }}>Approach 1</Text>
        <Text style={{ fontSize: 20 }}>
          Rerender Count : {approach1ReRenderCount1++}
        </Text>

        <View style={{ height: 20, marginTop: 10 }}>
          <LottieAnimated
            style={{
              alignSelf: "center",
              width: "100%",
            }}
            progress={progress}
            source={require("./progress")}
            autoPlay={false}
            loop={false}
          />
        </View>
        <PanGestureHandler onGestureEvent={gestureHandler}>
          <Animated.View style={[{ height: 20 }]}>
            <Animated.View
              style={[
                {
                  backgroundColor: "blue",
                  height: 20,
                  width: 20,
                  borderRadius: 10,
                },
                animatedStyle,
              ]}
            />
            <View
              style={{
                backgroundColor: "black",
                height: 2,
                width: "100%",
                position: "absolute",
                top: 10,
              }}
            />
          </Animated.View>
        </PanGestureHandler>
      </View>
    </SafeAreaView>
  );
};

const LottieAnimated = Animated.createAnimatedComponent(Lottie);
let approach2ReRenderCount = 0;
const Approach2: () => Node = () => {
  const [width, setWidth] = useState(1);
  const x = useSharedValue(0);

  const lottieAnimatedProps = useAnimatedProps(() => progress: x.value / width);

  const gestureHandler = useAnimatedGestureHandler({
    onStart: (event, ctx) => {
      x.value = event.absoluteX;
    },
    onActive: (event, ctx) => {
      x.value = event.absoluteX;
    },
  });

  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateX: x.value,
        },
      ],
    };
  });

  return (
    <SafeAreaView>
      <View
        style={{
          height: 150,
          paddingHorizontal: 20,
          borderWidth: 1,
          margin: 10,
          justifyContent: "center",
        }}
        onLayout={({
          nativeEvent: {
            layout: { width },
          },
        }) => {
          setWidth(width);
        }}
      >
        <Text style={{ fontSize: 20, paddingBottom: 10 }}> Approach 2</Text>
        <Text style={{ fontSize: 20 }}>
          Rerender Count : {approach2ReRenderCount++}
        </Text>
        <View style={{ height: 20, marginTop: 10 }}>
          <LottieAnimated
            animatedProps={lottieAnimatedProps}
            style={{
              alignSelf: "center",
              width: "100%",
            }}
            source={require("./progress")}
            autoPlay={false}
            loop={false}
          />
        </View>
        <PanGestureHandler onGestureEvent={gestureHandler}>
          <Animated.View style={[{ height: 20 }]}>
            <Animated.View
              pointerEvents="none"
              style={[
                {
                  backgroundColor: "blue",
                  height: 20,
                  width: 20,
                  borderRadius: 10,
                },
                animatedStyle,
              ]}
            />
            <View
              pointerEvents="none"
              style={{
                backgroundColor: "black",
                height: 2,
                width: "100%",
                position: "absolute",
                top: 10,
              }}
            />
          </Animated.View>
        </PanGestureHandler>
      </View>
    </SafeAreaView>
  );
};
```

## Links

- [Human page](https://www.bigbinary.com/blog/running-react-native-dependent-animations-on-ui-thread-using-reanimated)
