React Native custom progress indicator with Reanimated library
Tags: reactnative, reanimated, tutorial
Published: 2022-05-18 21:40:00

Hello folks! In this tutorial I would like to show how create high performant custom progress indicator with react-native-reanimated library. 

Video version of this tutorial available on 

{% youtube SSlmFWp0UG8 %}



Let's start with following template on [github](https://github.com/dimaportenko/reanimated-progress-indicator-tutorial/tree/template) or [expo snack](https://snack.expo.dev/@plabpreview/github.com-dimaportenko-reanimated-progress-indicator-tutorial@template). 

```typescript
export const ProgressIndicator: FC<{
  count?: number;
  itemWidth?: number;
  itemHeight?: number;
  duration?: number;
  itemsOffset?: number;
  topScale?: number;
}> = ({
  count = 8,
  itemWidth = 16,
  itemHeight = 4,
  duration = 5000,
  itemsOffset = 4,
  topScale = 4,
}) => {
  return (
    <View
      style={{
        flexDirection: "row",
        justifyContent: "space-between",
        alignItems: "center",
        height: itemHeight * topScale,
        width: (itemWidth + itemsOffset) * count,
      }}
    >
      {[...Array(count)].map((x, index) => (
        <ProgressItem
          key={`progressItem${index}`}
          index={index}
          width={itemWidth}
          height={itemHeight}
          count={count}
          topScale={topScale}
        />
      ))}
    </View>
  );
};

export const ProgressItem: FC<{
  index: number;
  count: number;
  width: number;
  height: number;
  topScale: number;
}> = ({ index, width, height, count, topScale }) => {
  return (
    <View
      style={[
        {
          width,
          height,
          backgroundColor: "black",
        },
      ]}
    />
  );
};
```

So basically we have `ProgressIndicator` with styling props which renders `count` of `ProgressItem` components. `ProgressItem` is simply black rectangle. To achieve final result we will scale Y axis of rectangles in sequence.

First we need to add animated value and change it with timing animated function from `react-native-reanimated` library.

```typescript
export const ProgressIndicator = ({duration, ...props}) => {
  const progress = useSharedValue(0);

  useEffect(() => {
    progress.value = withTiming(1, {
      duration,
    });
  }, []);
  // ...
};
```
We created [shared value](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/shared-values) `progress` with value 0. And start change it [withTiming](https://docs.swmansion.com/react-native-reanimated/docs/api/animations/withTiming) from 0 to 1 in `duration` milliseconds. 

Now let's pass `progress` value to the `ProgressItem` and animate scale of Y axis. 

```typescript
// ...
<ProgressItem
  // ...
  progress={progress}
/>
// ...
```

And use animated style in the Animated View.

```typescript
  export const ProgressItem = ({ index, width, height, count, topScale, progress }) => {
  const animatedStyle = useAnimatedStyle(() => {

    const scaleY = interpolate(
      progress.value,
      [0, 1], // input progress value from 0 to 1
      [1, topScale], // output scale from 1 to 4 (topScale = 4)
      Extrapolation.CLAMP
    );
    return {
      transform: [{ scaleY }],
    };
  });
  return (
    <Animated.View
      style={[
        {
          width,
          height,
          backgroundColor: "black",
        },
        animatedStyle
      ]}
    />
  );
};
```
Basically what we did [interpolate](https://docs.swmansion.com/react-native-reanimated/docs/api/miscellaneous/interpolate) scaleY value from 1 to 4 inside [useAnimatedStyle](https://docs.swmansion.com/react-native-reanimated/docs/api/hooks/useAnimatedStyle) over `duration` milliseconds.

![Scale from 1 to 4](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/epyexc12xd6fflotcfaq.gif)

Now instead of scale all items simultaneously let's do it one by one. For this we need to split our animation progress for the item count.

```typescript
const scaleY = interpolate(
  progress.value,
  [index / count, (index + 1) / count],
  [1, topScale],
  Extrapolation.CLAMP
);
```

![Scale up one by one](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gr77ywbge3kya78pupxc.gif)

Next step I would like to do is scale down before animate next item. In this case we need 3 output points [1, 4, 1] for each item. 

```typescript
const scaleY = interpolate(
  progress.value,
  [index / count, (index + 1) / count, (index + 2) / count],
  [1, topScale, 1],
  Extrapolation.CLAMP
);
```

And here we are

![Up and down animation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dj2qjvwpawo3f1twnagt.gif)

Now let's do our wave more smooth. We'll start next item animation earlier. To do it we split each progress piece by 3.

```typescript
const parts = 3;
const wholeCount = count * 3; 
const scaleY = interpolate(
  progress.value,
  [index / wholeCount, (index + parts) / wholeCount, (index + 2 * parts) / wholeCount],
  [1, topScale, 1], 
  Extrapolation.CLAMP
);
```

Almost what we want but animations finished a bit earlier than our `duration`. To use whole `duration` our 3 point value for last index should be equal 1. 

```
(index + 2 * parts) / wholeCount = 1
// where 
index = count - 1
// so
wholeCount = count - 1 + 2 * parts;
```
then
```typescript
const parts = 3;
const wholeCount = count - 1 + 2 * parts; 
const scaleY = interpolate(
  progress.value,
  [index / wholeCount, (index + parts) / wholeCount, (index + 2 * parts) / wholeCount],
  [1, topScale, 1], 
  Extrapolation.CLAMP
);
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z0c9whqdt9ggm2k0hor7.gif)

Great! We are almost there. Our animation looks good, but now it plays only once after component mount. To fix this let's use [withRepeat](https://docs.swmansion.com/react-native-reanimated/docs/api/animations/withRepeat) animation function. And we'll wrap our `withTiming` function like this

```typescript
useEffect(() => {
  progress.value = withRepeat(
    withTiming(1, { duration }),
    -1,
    true
  );
}, []);
```

First argument is our `withTiming` function. Second is number of repetitions if negative it will be infinity loop. Third is `reverse` param, which means it will play our animations back and forth. Basically it will change our progress value from 0 to 1, then from 1 to 0 and repeat. In case `reverse` is false it will change value from 0 to 1, jump back and 0 to 1 again. 

![Final gif of progress indicator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0ftgpy3cxv56l2qtau6.gif)

That's it! Please let me know what you think in the comments and of course feel free to ask any questions. Final code is available on [snack](https://snack.expo.dev/@plabpreview/github.com-dimaportenko-reanimated-progress-indicator-tutorial@devto) and on [github](https://github.com/dimaportenko/reanimated-progress-indicator-tutorial).