DEV Community

Cover image for Expo Router Tab Navigation -From the Docs
Aaron K Saunders
Aaron K Saunders

Posted on • Edited on

Expo Router Tab Navigation -From the Docs

Overview

Welcome to the second installment of my Expo Router file-based routing video series! In this series, I will be diving into various aspects of Expo Router, including basic routing, dynamic routes, and the Tab Navigator.

As I delved into working with the new router, I initially started with the basic two-tab template, which has been a valuable starting point. However, there were instances where I found myself puzzled by the magic happening behind the scenes.

To address this, I decided to take a step back and create these videos from scratch. Together, we will add Expo Router to a basic Expo application and follow the documentation closely as we build the application. Along the way, I'll be sharing all the steps I take in detail, as I felt the existing documentation on using the Tab Navigator from scratch wasn't very clear, in my honest opinion.

I'm excited about this journey, and I hope you'll join me as we explore Expo Router and its functionalities. Feel free to share this content with your friends and associates as well—let's learn and grow together!

The Steps

Run this command to build the app project

npx create-expo-app@latest --template blank@sdk-49  my-app-tabs-expo-router
Enter fullscreen mode Exit fullscreen mode

Change into the project directory and install the additional dependencies

npx expo install expo-router@latest react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar react-native-gesture-handler
Enter fullscreen mode Exit fullscreen mode

Open the package.json file and make the following update

{
  "main": "expo-router/entry"
}
Enter fullscreen mode Exit fullscreen mode

Not using web so skipping that part of documentation, but add the scheme app.json

"scheme": "myapptabsexporouter",
Enter fullscreen mode Exit fullscreen mode

update babel.config.js to include the new plugin.

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: ['expo-router/babel'],
  };
};
Enter fullscreen mode Exit fullscreen mode

Lets add the index page and then test it is working, remember the files all live in a new app directory that you have to create. /app/index.js

// /app/index.js

import { Text } from 'react-native';

export default function Page() {
  return <Text>Home page</Text>;
}
Enter fullscreen mode Exit fullscreen mode

lets add a group for tabs by creating a folder with the folder name "tabs" in parenthesis, /app/(tabs)

(tabs)
Enter fullscreen mode Exit fullscreen mode

then add a new _layout.js file in the "(tabs)" folder. The directory should look like this /app/(tabs)/_layout.js. We are adding the Tabs UI component and adding one Tab called "Home".

import { Tabs } from "expo-router";

export default function TabsLayout() {
  return (
    <Tabs screenOptions={{ headerShown: false }}>
      <Tabs.Screen
        name="home"
        options={{
          tabBarLabel: "Home",
          title: "Home"
        }}
      />
    </Tabs>
  );
}
Enter fullscreen mode Exit fullscreen mode

Create a new folder name "home" and add an index.js to that folder. The new file will be the index page of the Home tab stack. The directory should look like this /app/(tabs)/home/index.js

// /app/(tabs)/home/index.js

import { Text } from 'react-native';

export default function Page() {
  return <Text>Index page of Home Tab</Text>;
}
Enter fullscreen mode Exit fullscreen mode

But we need a layout for the home folder, so add a new file with the contents below. /app/(tabs)/home/_layout.js

// /app/(tabs)/home/_layout.js

import { Stack } from "expo-router";

export default function HomeLayout() {
  return <Stack />;
}
Enter fullscreen mode Exit fullscreen mode

Now we need to update the /app/index.js to redirect to the /app/(tabs)/home at start up.

// /app/index.js

import { Redirect } from 'expo-router';
import { Text } from 'react-native';

export default function Page() {
  return <Redirect href={"/(tabs)/home"} />;
}
Enter fullscreen mode Exit fullscreen mode

We then repeat the process for the second tab called "Settings", first update the _layout.js in the tabs group/folder to include the second tab. It will be called settings /app/(tabs)/_layout.js

// /app/(tabs)/_layout.js

import { Tabs } from "expo-router";

export default function TabsLayout() {
  return (
    <Tabs screenOptions={{ headerShown: false }}>
      <Tabs.Screen
        name="home"
        options={{
          tabBarLabel: "Home",
          title: "Home",
        }}
      />
      <Tabs.Screen
        name="settings"
        options={{
          tabBarLabel: "Settings",
          title: "Settings",
        }}
      />
    </Tabs>
  );
}
Enter fullscreen mode Exit fullscreen mode

To make things easier, just copy the home folder and rename the new copy to settings, then edit the index.js file in the settings folder with the following code.

// /app/(tabs)/settings/index.js

import { Text } from 'react-native';

export default function Page() {
  return <Text>Index page of Settings Tab</Text>;
}
Enter fullscreen mode Exit fullscreen mode

At this point you should have the basic two tabs app running

Some Extras

Now lets fix the page titles, first the Settings. Add a Stack.Screen element and set the options

// /app/(tabs)/settings/index.js

import { Stack } from "expo-router";
import { Text, View } from "react-native";

export default function Page() {
  return (
    <View>
      <Stack.Screen options={{ headerShown: true, title: "Settings" }} />
      <Text>Index page of Settings Tab</Text>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Then the Home

// /app/(tabs)/home/index.js

import { Stack } from "expo-router";
import { Text, View } from "react-native";

export default function Page() {
  return (
    <View>
      <Stack.Screen options={{ headerShown: true, title: "Home" }} />
      <Text>Index page of Home Tab</Text>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Now lets show some nested navigation in the Home Tab, add a link to a new page, /app/(tabs)/settings/index.js which when clicked with navigate to the new page next-page

// /app/(tabs)/home/index.js

export default function Page() {
  return (
    <View>
      <Stack.Screen options={{ headerShown: true, title: "Home" }} />
      <Text>Index page of Home Tab</Text>
      <Link href={"/home/next-page"} style={{ marginTop: 16 }}>
        <Text style={{ fontWeight: "bold" }}>Go To Next Page</Text>
      </Link>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Create the new page next-page.js in the Home folder

// /app/(tabs)/home/next-page.js

import { Stack } from "expo-router";
import { Text, View } from "react-native";

export default function NextPage() {
  return (
    <View>
      <Stack.Screen options={{ headerShown: true, title: "Next Page" }} />
      <Text>Next page of Home Tab</Text>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

Lets add some icons to our tab buttons; import the icons in the /(tabs)/_layout.js file

// /app/(tabs)/_layout.js

import FontAwesome from "@expo/vector-icons/FontAwesome";
Enter fullscreen mode Exit fullscreen mode

Then for the home tab icon, make the following change to the Tab.Screen for the Home, add this as a new option /(tabs)/_layout.js

// /app/(tabs)/_layout.js

   tabBarIcon: ({ color }) => (
      <FontAwesome
        size={28}
        style={{ marginBottom: -3 }}
        name="home"
        color={color}
      />
    ),

Enter fullscreen mode Exit fullscreen mode

Then do the same for the Settings, but change the name of the icon from “home” to “gear” , add this as a new option

// /app/(tabs)/_layout.js

tabBarIcon: ({ color }) => (
  <FontAwesome
    size={28}
    style={{ marginBottom: -3 }}
    name="home"
    color={color}
  />
),
Enter fullscreen mode Exit fullscreen mode

When you are done the layout should look like this

// /app/(tabs)/_layout.js

import { Tabs } from "expo-router";
import FontAwesome from "@expo/vector-icons/FontAwesome";

export default function TabsLayout() {
  return (
    <Tabs screenOptions={{ headerShown: false }}>
      <Tabs.Screen
        name="home"
        options={{
          tabBarLabel: "Home",
          title: "Home",
          tabBarIcon: ({ color }) => (
            <FontAwesome
              size={28}
              style={{ marginBottom: -3 }}
              name="home"
              color={color}
            />
          ),
        }}
      />
      <Tabs.Screen
        name="settings"
        options={{
          tabBarLabel: "Settings",
          title: "Settings",
          tabBarIcon: ({ color }) => (
            <FontAwesome
              size={28}
              style={{ marginBottom: -3 }}
              name="gear"
              color={color}
            />
          ),
        }}
      />
    </Tabs>
  );
}
Enter fullscreen mode Exit fullscreen mode

Wrap

Well I hope you found this helpful, I think it is a bit more detailed than what the documentation provided and it can also be beneficial to those looking for something without typescript.

Links

Social Media

YouTube - https://www.youtube.com/@AaronSaundersCI
Twitter - https://twitter.com/aaronksaunders
Facebook - https://www.facebook.com/ClearlyInnovativeInc
Instagram - https://www.instagram.com/aaronksaunders/
Dev.to - https://dev.to/aaronksaunders

Top comments (5)

Collapse
 
collinsanele profile image
Collins Anele

👍

Collapse
 
andremendoncabhz profile image
André Luiz Mendonça • Edited

[BABEL]: expo-router/babel is deprecated in favor of babel-preset-expo in SDK 50. To fix this issue, remove "expo-router/babel" from "plugins" in your babel.config.js file

ConfigError: Cannot resolve entry file: The main field defined in your package.json points to an unresolvable or non-existent path. To fix this issue, change main field to "node_modules/expo/AppEntry.js".

Collapse
 
scottlexium profile image
Scottlexium

when you use the showHeader option in the home file in expo-router 2.0.0 I am getting a warning

WARN Theredirectprop on <Screen /> is deprecated and will be removed. Please userouter.redirectinstead

<Stack.Screen options={{ headerShown: true, title: "Home" }} /> 
Enter fullscreen mode Exit fullscreen mode

placing this in the home/index.tsx or js file fixed the issue for me

import { Stack } from "expo-router";
export default function HomeLayout() {
    return <Stack screenOptions={
        {
            headerShown: false,
        }
    } />;
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
karltaylor profile image
Karl Taylor

So you need to redirect to a tab if you don't want an index.js? expo router is such a weird setup...

Collapse
 
danielxart profile image
Daniel

What If I want the default tabs page to be a dynamic one? In the docs they say to ensure the dynamic page always have the same parameter, but thats exactly what not dynamic means! XD.
Id like to make a mural of posts, and when a user click a post they are sent to a tabs navigation styl where they can either see the post content or swipe to see the comments in the next tab. It seems like I want a dynamic __layout page in (tabs), but that seems wrong. How can I achieve that behaviour?