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
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
Open the package.json
file and make the following update
{
"main": "expo-router/entry"
}
Not using web so skipping that part of documentation, but add the scheme app.json
"scheme": "myapptabsexporouter",
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'],
};
};
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>;
}
lets add a group for tabs by creating a folder with the folder name "tabs" in parenthesis, /app/(tabs)
(tabs)
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>
);
}
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>;
}
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 />;
}
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"} />;
}
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>
);
}
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>;
}
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>
);
}
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>
);
}
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>
);
}
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>
);
}
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";
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}
/>
),
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}
/>
),
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>
);
}
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
- Expo Router Doc - https://docs.expo.dev/routing/installation/#manual-installation
- Stack Navigation Video - https://youtu.be/wBjy08dxL8I
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)
👍
[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 yourpackage.json
points to an unresolvable or non-existent path. To fix this issue, changemain
field to "node_modules/expo/AppEntry.js".when you use the showHeader option in the home file in expo-router 2.0.0 I am getting a warning
WARN The
redirectprop on <Screen /> is deprecated and will be removed. Please use
router.redirectinstead
placing this in the home/index.tsx or js file fixed the issue for me
So you need to redirect to a tab if you don't want an
index.js
? expo router is such a weird setup...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?