Animated Sliding tab bar in React Native
Tags: reactnative, animation, reactnavigation
Published: 2020-04-08 10:16:17

I'll show you in this tutorial how to make this cool animated custom bottom tab bar using React Navigation.

<p align="center">
    <img width="500" alt="typescript-starter dark logo" src="https://media.giphy.com/media/ZZfnKe0Ccwq15vzf6E/giphy.gif" style="max-width:100%;">
</p>

In order to do this, I found this great tutorial by [@ksushiva](https://dev.to/ksushiva).

https://dev.to/ksushiva/animated-sliding-tab-bar-in-react-native-56nb

But unfortunately, it's already outdated due to the recent publication of React Navigation v5. And it comes with [lots of modifications](https://reactnavigation.org/docs/upgrading-from-4.x/).

# Dependencies
```
npm install @react-navigation/native

npm install @react-navigation/bottom-tabs

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
```

# Custom MenuItem
```
import React from "react";
import { View } from "react-native";
import { blue, grey } from "../../styles";
import { AntDesign } from "@expo/vector-icons";
type Props = {
  iconName: string;
  isCurrent?: boolean;
};
export const BottomMenuItem = ({ iconName, isCurrent }: Props) => {
  return (
    <View
      style={{
        height: "100%",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <AntDesign
        name={iconName}
        size={32}
        style={{ color: isCurrent ? blue : grey }}
      />
    </View>
  );
};
```
This represented each item in the tab bar. You can adjust this component with anything you want. Here it requires an `iconName` in order to display the correct icon and a `isCurrent` property which changes the icon color if it is currently selected.

# The Custom TabBar component

```
import React, { useState } from "react";
import {
  View,
  TouchableOpacity,
  Dimensions,
  StyleSheet,
} from "react-native";
import { BottomTabBarProps } from "@react-navigation/bottom-tabs";
import { BottomMenuItem } from "./BottomMenuItem";
import { blue } from "../../styles";
export const TabBar = ({
  state,
  descriptors,
  navigation,
}: BottomTabBarProps) => {
  const totalWidth = Dimensions.get("window").width;
  const tabWidth = totalWidth / state.routes.length;
  return (
    <View style={[style.tabContainer, { width: totalWidth }]}>
      <View style={{ flexDirection: "row" }}>
        <View style={style.slider}/>
{state.routes.map((route, index) => {
          const { options } = descriptors[route.key];
          const label =
            options.tabBarLabel !== undefined
              ? options.tabBarLabel
              : options.title !== undefined
              ? options.title
              : route.name;
const isFocused = state.index === index;
const onPress = () => {
            const event = navigation.emit({
              type: "tabPress",
              target: route.key,
              canPreventDefault: true,
            });
if (!isFocused && !event.defaultPrevented) {
              navigation.navigate(route.name);
            }
const onLongPress = () => {
            navigation.emit({
              type: "tabLongPress",
              target: route.key,
            });
          };
return (
            <TouchableOpacity
              accessibilityRole="button"
              accessibilityStates={isFocused ? ["selected"] : []}
              accessibilityLabel={options.tabBarAccessibilityLabel}
              testID={options.tabBarTestID}
              onPress={onPress}
              onLongPress={onLongPress}
              style={{ flex: 1 }}
              key={index}
            >
              <BottomMenuItem
                iconName={label.toString()}
                isCurrent={isFocused}
              />
            </TouchableOpacity>
          );
        })}
      </View>
    </View>
  );
};
const style = StyleSheet.create({
  tabContainer: {
    height: 60,
    shadowOffset: {
      width: 0,
      height: -1,
    },
    shadowOpacity: 0.1,
    shadowRadius: 4.0,
    backgroundColor: "white",
    borderTopRightRadius: 20,
    borderTopLeftRadius: 20,
    elevation: 10,
    position: "absolute",
    bottom: 0,
  },
  slider: {
    height: 5,
    position: "absolute",
    top: 0,
    left: 10,
    backgroundColor: blue,
    borderRadius: 10,
    width: 50
},
});
```

# Connect the custom TabBar to the Navigation System (BottomMenu component)

```
import React from "react";
import {
  createBottomTabNavigator,
  BottomTabBarProps,
} from "@react-navigation/bottom-tabs";
import { TabBar } from "./TabBar";
import { AppsScreen } from "../../screens/AppsScreen";
import { DashboardScreen } from "../../screens/DashboardScreen";
import { GroupScreen } from "../../screens/GroupScreen";
import { ProfileScreen } from "../../screens/ProfileScreen";
import { useSafeArea } from "react-native-safe-area-context";
import { View } from "react-native";
export const BottomMenu = () => {
  const Tab = createBottomTabNavigator();
  return (
    <View style={{ flex: 1, position: "relative"}}>
      <Tab.Navigator
        tabBar={(props: BottomTabBarProps) => <TabBar {...props} />}
      >
        <Tab.Screen name="search1" component={AppsScreen} />
        <Tab.Screen name="dashboard" component={DashboardScreen} />
        <Tab.Screen name="profile" component={GroupScreen} />
        <Tab.Screen name="user" component={ProfileScreen} />
      </Tab.Navigator>
      {useSafeArea().bottom > 0 && (
        <View
          style={{
            height: useSafeArea().bottom - 5,
            backgroundColor: "white",
          }}
        />
      )}
    </View>
  );
};
```
we use the `useSafeArea` piece of code in order to render our tab bar higher if there is the horizontal bar on the recent iOS devices for example.

Then you simply have to place this `BottomMenu` anywhere. In `App` component for example:

```
import React from "react";
import { SafeAreaProvider } from "react-native-safe-area-context";
import { NavigationContainer } from "@react-navigation/native";
import {BottomMenu} from "./src/components/BottomMenu/BottomMenu";
export default function App() {
  return (
    <NavigationContainer>
      <SafeAreaProvider>
          <BottomMenu/>
      </SafeAreaProvider>
    </NavigationContainer>
  );
}
```

Now you should have the bottom tab bar on your app but we need to animate the "slider" now.

So in the `TabBar` component, you need:
To add a state variable :
```
const [translateValue] = useState(new Animated.Value(0));
```
To change the current `View` representing the slider to :
```
<Animated.View
          style={[
            style.slider,
            {
              transform: [{ translateX: translateValue }],
              width: tabWidth - 20,
            },
          ]}
        />
```

And finally, in the `onPress` function, add this piece of code:
```
Animated.spring(translateValue, {
              toValue: index * tabWidth,
              velocity: 10,
              useNativeDriver: true,
            }).start();
```
---
You can find the entire code example in this repository:
https://github.com/baptisteArnaud/animated-bottom-tab-react-native-example

Feel free to post a comment on this tutorial if you need help.