With the launch of react native v5 about 3 months ago backed by neat documentation making life easier with hooks usage and native transitions, we decided to revamp our current navigation.
Problem:
Build a navigation system for a an app serving to showcase news content such as collections and stories with configurable paywall to facilitate pay per article / premium content across devices
Design
- Main screen consists of bottom tab navigation of about 4 tabs
- Listing screens to showcase categories / sections
- Content screens to display content
Architecture:
App.js
The root of your application needs to be enclosed within a navigation controller, the children of which would be the stack of screens. This will helps us separate concerns b/w auth flows such as login stack and other flows in our case
import { NavigationContainer, useLinking } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { enableScreens } from "react-native-screens";
enableScreens();
const [initialState, setInitialState] = useState();
<NavigationContainer initialState={initialState} ref={ref}>
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName="modalStack"
>
<Stack.Screen name="modalStack" component={ModalStack} />
<Stack.Screen name="loginStack" component={LoginStack} />
</Stack.Navigator>
</NavigationContainer>
modal-stack.js
The stack is composed of multiple screens which relate in terms of genre in our case.
The noticeable aspect is that the stacks are nested, BottomTabStack
is nested within the modal stack and that will be the initial route where our app should land post launch
import React from "react";
import { BottomTabStack } from "../bottom-tab-navigators/bottom-tab-stack";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { SCREEN_LIST, STACK_LIST } from "../../constants/screen-mapping";
/* Import screen components such as AuthorsScreen, StoryScreen ... */
const Stack = createNativeStackNavigator();
export const ModalStack = () => (
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName={STACK_LIST.bottomTabStack}
>
<Stack.Screen name={SCREEN_LIST.authorScreen} component={AuthorsScreen} />
<Stack.Screen name={SCREEN_LIST.storyScreen} component={StoryScreen} />
<Stack.Screen name={SCREEN_LIST.bookMarkScreen} component={BookMarkScreen} />
<Stack.Screen name={STACK_LIST.bottomTabStack} component={BottomTabStack} />
</Stack.Navigator>
);
bottom-tab-stack.js
import React from "react";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
/* Import necessary constants and components.. */
const Tab = createBottomTabNavigator();
const tabBarStyle = {
activeTintColor: "blue",
style: {
paddingTop: 5
}
};
const TabIcons = (focused, icon) => {
const iconColor = focused ? "blue" : "black"; /* highlight with a diff color if focused */
switch (icon) {
case "home":
return <HomeIcon color={iconColor} />;
case "sections":
return <SectionIcon color={iconColor} />;
case "search":
return <SearchIcon color={iconColor} />;
case "my-app":
return <MyAppIcon color={iconColor} />;
default:
break;
}
};
export const BottomTabStack = () => (
<Tab.Navigator tabBarOptions={tabBarStyle}>
<Tab.Screen
name={BOTTOM_TAB.bottomHomeTab}
component={HomeScreen}
options={{
tabBarIcon: ({ focused }) => TabIcons(focused, "home"),
title: "Home",
tabBarTestID: "test-id-home" /* test id's for use by automation test tools */
}}
/>
<Tab.Screen
name={BOTTOM_TAB.bottomCategoryTab}
component={CategoryScreen}
options={{
tabBarIcon: ({ focused }) => TabIcons(focused, "sections"),
title: "Sections",
/* Unique identifier to target category tab */
tabBarTestID: "test-id-categ"
}}
/>
<Tab.Screen
name={BOTTOM_TAB.bottomSearchTab}
component={SearchScreen}
options={{
tabBarIcon: ({ focused }) => TabIcons(focused, "search"),
title: "Search",
tabBarTestID: "test-id-search"
}}
/>
<Tab.Screen
name={BOTTOM_TAB.bottomProfileTab}
component={ProfileScreen}
options={{
tabBarIcon: ({ focused }) => TabIcons(focused, "my-app"),
title: "My App",
tabBarTestID: "test-id-profile"
}}
/>
</Tab.Navigator>
);
login-stack.js
This stack abstracts the auth-flow.
import React from "react";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
/* Import screen components such as Login, SignUp ... */
const Stack = createNativeStackNavigator();
export const LoginStack = () => (
<Stack.Navigator
screenOptions={{
headerShown: false
}}
>
<Stack.Screen name={SCREEN_LIST.loginScreen} component={Login} />
<Stack.Screen name={SCREEN_LIST.registrationScreen} component={SignUp} />
<Stack.Screen name={SCREEN_LIST.forgotPasswordScreen} component={ForgotPassword} />
</Stack.Navigator>
);
With that being set, we can begin surfing 🏄 through the screens
If we are navigating within a stack:
navigation.navigate("<<screen_name>>", {<<key>>: <<val>>});
If we do need to navigate across stacks:
navigation.navigate("<<stack_name>>", {
screen: <<screen_name>>,
params: {<<param_key>> : <<param_val>>}
});
There are a lot of ground to cover with react-navigation such as events, lifecycles and much more ..
Will be dropping in a github repo link for an example soon 🔥
If you have questions, let us know in the comments and we are looking forward for your feedback 🍻
Top comments (0)