DEV Community

themmyloluwaa
themmyloluwaa

Posted on • Edited on

React-Native Custom Tab Component | ReactNative-Navigation.

Update I wrote about working with React Navigation 5 and covered all available navigations. Check it out here

About a month 🎡🎡🎡🎡 ago, I started to learn react native and wanted to build a small project that taught me the basics of react native and some key app features like navigation, styling, and every sweet feature react-native had to offer.

So here I was, a curious, passionate learner that I am, having come from a react background, I felt confident that react native should be an easy peasy kind of technology to learn. Little did I know that I was setting up myself for planetary devastation πŸ’₯πŸ’₯πŸ’₯πŸ’₯πŸ’₯πŸ’₯πŸ’₯.

planetary devastation image

I had built all my components, views, texts, styles, and everything nice. It was time to tie everything together by integrating navigation into my superpower puffed app.

While they are many packages out there that enable you to add navigation to your react native app, I decided to opt-in for the most popular package out there, which is react-navigation. React navigation comes with a built-in tab navigation feature, stack navigation, switch navigation, and also drawer navigation. These types of navigations enable you to build and add dynamic navigation features to your project.

I was ecstatic to find this fantastic package by the expo team and the react-native community. Still, sooner than later, my excitement turned into days of frustration as I tried to figure out how to implement navigation that's similar to twitter's profile page navigation.

Twitter profile navigation

The Solution

React navigation also has built-in support for you to create your custom navigation, and this was my approach to building the navigation I needed to finish up my super excellent mobile application.

While there's a dedicated documentation page and some articles on the web about building your custom navigation, I couldn't find the solution to my use case and so here's me writing this out there to the world for someone who needs to create their custom navigation for their superpower puff girls app 😎😎😎😎😎.

What do we need

Expo CLI version 36.0 latest
Visual studio but you could use any text editor of your choice
react navigation
And durrhh your apron cause we about to cook some magical apps.

Getting started

This project already assumes you are familiar with starting a project in expo. Please refer to the documentation to learn how to initialize a react-native app in expo.

Once you have initialized it, install the react native navigation packages, react-navigation-tabs, react-native-gesture-handler, react-native-reanimated, react-native-screens, react-native-safe-area-context, @react-native-community/masked-view.
For this article, we would be building only the navigation header of this image.

Twitter profile navigation

It is also important to note that this tutorial does not take into account the animation gestures or features that react-native has, please dive into the react native animation section in the docs

Navigate to your Custom Header folder, in there, create three folders, components to hold our components, screen to hold our screens and navigation to hold our navigations.

In your components folder, create a new file called Header.js. This is where we'd be creating our custom react native navigation header.

Navigate to your header.js file and add the following lines of code.

import React from "react";

import { View, Text, StyleSheet } from "react-native";

const Header = props => {
  return (
    <View style={styles.containerHeader}>
      <View style={styles.textContainer}>
        <Text style={styles.textWhite}>Holi</Text>
        <Text style={styles.textWhite}>1,004 tweets</Text>
      </View>
      <View style={styles.tabContainer}>
        <View>
          <Text>Tweets</Text>
        </View>
        <View>
          <Text>Tweets & Replies</Text>
        </View>
        <View>
          <Text>Media</Text>
        </View>
        <View>
          <Text>Likes</Text>
        </View>
      </View>
    </View>
  );
};
Enter fullscreen mode Exit fullscreen mode

Add the following styles to it. For this tutorial, we would be making use of a background color for our background for the header component we just created. I use black, but feel free to chose anyone you want, and don't forget to export the function.

const styles = StyleSheet.create({
  containerHeader: {
    flex: 1,
    flexDirection: "column",
    justifyContent: "center",
    alignItems: "center"
  },
  textContainer: {
    marginTop: 70
  },
  textWhite: {
    color: "black",
    marginVertical: 10
  },
  tabContainer: {
    backgroundColor: "white",
    width: "100%",
    flexDirection: "row",
    justifyContent: "space-between",
    paddingHorizontal: 10,
    height: "20%",
    alignItems: "center",
    marginTop: 10,
    height: 40
  }
});
export default Header;
Enter fullscreen mode Exit fullscreen mode

Import your header.js file in the entry of your app, the App.js file, and include the following styles. For styling purposes, our app container has a background color of #eef;

import React from "react";
import { StyleSheet, Text, View } from "react-native";
import Header from "./components/Header";

export default function App() {
  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Header />
      </View>
      <View style={styles.childContainer}>
        <Text style={{ fontSize: 30 }}>I am badass 🐐🐐🐐</Text>
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#eef",
    flexDirection: "column"
  },
  childContainer: {
    justifyContent: "center",
    alignItems: "center",
    marginVertical: 100
  },
  header: {
    backgroundColor: "cyan",
    width: "100%",
    height: "15%"
  }
});

Enter fullscreen mode Exit fullscreen mode

Here's how our app looks so far.
Custom header navigation app look

From here, we can proceed to implement our navigation feature into our super excellent cool application.

Navigate to your screen folder and create the tweet screen, tweets & replies screen, media screen, and likes screen. For this tutorial, we have a basic view with one text component. Copy and paste this code into each file.

import React from "react";
import { View, Text } from "react-native";

Class Tweets extends React.Component {
  render() {
    return (
       <View
        style={{ justifyContent: "center", alignItems: "center", height: 400 }}
      >
        <Text>I am the Tweets screen 🐐🐐🐐🐐🐐🐐</Text>
      </View>
    );
  }
}

export default Tweets;
Enter fullscreen mode Exit fullscreen mode

Do this for all files created in the screen folder and rename them to the screen they are to represent.

Navigate to your navigation folder and create an index.js file. Import your required packages and each screen you already created in the screen folder.

import { createAppContainer } from "react-navigation";
import { createMaterialTopTabNavigator } from "react-navigation-tabs";
import Tweets from "../screens/Tweets";
import TweetNReplies from "../screens/TweetNReplies";
import Media from "../screens/Media";
import Likes from "../screens/Likes";
import Header from "../components/Header";

const TabNavigator = createMaterialTopTabNavigator(
  {
    Tweets: {
      screen: Tweets
    },
    TweetsNReplies: {
      screen: TweetNReplies
    },
    Media: {
      screen: Media
    },
    Likes: {
      screen: Likes
    }
  },
  {
    tabBarComponent: Header,
    tabBarOptions: {
      activeTintColor: "#1BA1F3",
      inactiveTintColor: "#000"
    },
    initialRouteName: "Tweets"
  }
);

const Navigation = createAppContainer(TabNavigator);

export default Navigation;

Enter fullscreen mode Exit fullscreen mode

From the react-navigation documentation, Containers are responsible for managing your app state and linking your top-level navigator to the app environment..

We import both createAppContainer and also createMaterialTopTabNavigator. createMaterialTopTabNavigator is what is responsible for helping us create our header navigations for our app. The createMaterialTopTabNavigator takes in different navigation options. Please check the documentation for a full list of all the options.
For the purpose of this tutorial, we're making use of three major options. We override the default navigation with our custom navigation header by using the tabBarComponent option. We set our default actual tint color and inactive tint color using the tabBarOptions and our initial route name to be the Tweets screen that we previously defined.

We then proceed to wrap our created tabNavigator with the createAppContainer and assign it to a variable called Navigation. Export the Navigation constant and import it in the root directory of your application. Our App.js file.

Replace the code inside your app.js file and header.js file, respectively, with the following code. This will also affect the styling of our app.

import React from "react";
import { StyleSheet, View } from "react-native";
import Navigation from "./navigation/index";
export default function App() {
  return (
    <View style={styles.container}>
      <Navigation />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#eef",
    flexDirection: "column"
  },
  childContainer: {
    justifyContent: "center",
    alignItems: "center"
  },
  header: {
    backgroundColor: "cyan",
    width: "100%"
  }
});
Enter fullscreen mode Exit fullscreen mode

header.js

import React from "react";

import { View, Text, StyleSheet } from "react-native";

const Header = props => {
  return (
    <View style={styles.containerHeader}>
      <View style={styles.textContainer}>
        <Text style={styles.textWhite}>Holi</Text>
        <Text style={styles.textWhite}>1,004 tweets</Text>
      </View>
      <View style={styles.tabContainer}>
        <View>
          <Text>Tweets</Text>
        </View>
        <View>
          <Text>Tweets & Replies</Text>
        </View>
        <View>
          <Text>Media</Text>
        </View>
        <View>
          <Text>Likes</Text>
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  containerHeader: {
    flexDirection: "column",
    justifyContent: "space-between",
    alignItems: "center",
    backgroundColor: "cyan",
    height: 150
  },
  textContainer: {
    marginVertical: 30,
    paddingTop: 30
  },
  textWhite: {
    color: "black"
  },
  tabContainer: {
    backgroundColor: "white",
    width: "100%",
    flexDirection: "row",
    justifyContent: "space-between",
    paddingHorizontal: 10,
    alignItems: "center",
    height: 40
  }
});
export default Header;
Enter fullscreen mode Exit fullscreen mode

Our super excellent duper app has this look.

custom tabbar navigation demo

The next step is to convert our header component to a functioning react-native tab bar component. Through the createAppContainer, we have access to the props and options that come with the react-navigation-tabs package.
Console logging props in our header.js file would reveal all the props available to the component.

To transform our component to a functioning tab bar, we need the following props;

navigationState: holds the state of the navigations. It also contains our route details.

navigation: an object that contains different methods like navigate, goBack, et.c.

activeTintColor: the colour of our screen navigator when active.

inactiveTintColor: the colour of our screen navigators when inactive.

Replace the code in your header.js file. We'd work through everything we did shortly.

import React from "react";

import { View, Text, StyleSheet, TouchableWithoutFeedback } from "react-native";

const Header = props => {
  const {
    navigationState,
    navigation,
    activeTintColor,
    inactiveTintColor
  } = props;
  const activeTabIndex = navigation.state.index;

  return (
    <View style={styles.containerHeader}>
      <View style={styles.textContainer}>
        <Text style={styles.textWhite}>Holi</Text>
        <Text style={styles.textWhite}>1,004 tweets</Text>
      </View>
      <View style={styles.tabContainer}>
        {navigationState.routes.map((route, index) => {
          const isRouteActive = index === activeTabIndex;
          const tintColor = isRouteActive ? activeTintColor : inactiveTintColor;

          return (
            <TouchableWithoutFeedback
              onPress={() => navigation.navigate(route.routeName)}
              key={route.routeName}
            >
              <View>
                <Text
                  style={{
                    fontSize: 17,
                    textTransform: "uppercase",
                    color: `${tintColor}`,
                    fontWeight: `${isRouteActive ? "bold" : "normal"}`
                  }}
                >
                  {route.routeName}
                </Text>
              </View>
            </TouchableWithoutFeedback>
          );
        })}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  containerHeader: {
    flexDirection: "column",
    justifyContent: "space-between",
    alignItems: "center",
    backgroundColor: "cyan",
    height: 150
  },
  textContainer: {
    marginVertical: 30,
    paddingTop: 30
  },
  textWhite: {
    color: "black"
  },
  tabContainer: {
    backgroundColor: "white",
    width: "100%",
    flexDirection: "row",
    justifyContent: "space-between",
    paddingHorizontal: 10,
    alignItems: "center",
    height: 40
  }
});
export default Header;

Enter fullscreen mode Exit fullscreen mode

Explanation

We first extract navigationState, navigation object, activeTintColor and inactiveTintColor from the props. We then proceed to store the index of our tab that's active in a constant called activeTabIndex. To ensure that the tabs are clickable, we import the TouchableWithoutFeedback component from react-native.

On the navigationState props is an array of our routes. We map this array and first check if the index of the current item in the array is equal to the activeTabIndex constant previously defined. We store this value in a constant called isRouteActive.

We store the tintColor of our tabs depending on if they are active or not by using the isRouteActive value defined earlier.

we then return our tab wrapped around TouchableWithoutFeedback and give it an onPress event to navigate to the desired tab by using the navigate method on the navigation object and pass our routeName to it as the value.

We set our tab title style by using the tintColor variable and isRouteActive variable and then render our routeName contained in the individual mapped route as the value in between the text component.

The result of our code above with a few modifications to the style is this.

custom navigation demo

Conclusion

We are done with our beautiful app, there are a lot of modifications that could improve our app, one is replacing the N in between TweetsNReplies with &. I leave this to you great problem solver to figure out. Another improvement is to move the mapped navigation routes into its separate file. This will improve readability and make our codebase neat. There's still a lot of improvements and features that can be implemented, like adding a back icon to navigate to the previous tab and all but you get the idea.

There's also a lot of details I didn't cover, tutorials and articles are not a replacement for documentation of the package or language, please dive into the documentation for all the details and things not covered.

Thank you for sticking this long with me, and please feel free to point our any correction, improvements, suggestions, contributions, or solution to the easter egg problem I mentioned above. See ya later.

Top comments (4)

Collapse
 
dauhn profile image
dauhn

I quite love this post! Now I am trying to make borderBottom when the tab is active like twitter's profile page navigation, but the problem I have is that the width is the same with the text width. Can you also let me know how to expand the border width?

Collapse
 
codekagei profile image
themmyloluwaa

Hi @dauhn my apologies for just replying. Have you been able to resolve this? If not can you please share an image? That would make debugging easier.

Collapse
 
amkaub profile image
amkaub

how to make this in version 5 navigation?

Collapse
 
codekagei profile image
themmyloluwaa • Edited

Hi amkaub, I haven't tried this out in version 5 but the docs is a great place to start. In version 5, they provide a tabBar props option that enables you to pass a react component as the tab bar header. You should check it out here and they also provide a good example.

reactnavigation.org/docs/material-...

If you get stuck, please do not hesitate to reach out. Cheers.