DEV Community

Cover image for Building VeLi: A Complete React Native Tab Navigation Tutorial
Fonyuy Gita
Fonyuy Gita

Posted on

Building VeLi: A Complete React Native Tab Navigation Tutorial

My Inspiration for VeLi (Vegetarian Living)

The idea for VeLi came from observing the growing movement toward plant-based lifestyles and the need for a simple, beautiful app that celebrates vegetarian food. I wanted to create something that feels fresh, organic, and inviting—just like the vegetables and plant-based meals we're showcasing.

The name "VeLi" is short for "Vegetarian Living," representing a lifestyle choice rather than just a diet. The app aims to inspire users to explore the colorful world of plant-based foods, discover new recipes, and connect with a community that values health, sustainability, and compassion.

I chose a green color palette because green is universally associated with nature, growth, and freshness. The specific shades I selected—from the deep forest green (#1B5E20) to the vibrant leaf green (#4CAF50)—create a sense of vitality and earth connection. These aren't just random greens; they're carefully chosen to feel mature and trustworthy while still being energetic and inviting.

Now let me walk you through building VeLi from the ground up, piece by piece, so you can see exactly how everything comes together and test each step along the way.

Part 1: Setting Up Your Foundation

First, let's create your project structure. Open your terminal and navigate to where you want to create your project, then run these commands one at a time:

npx create-expo-app VeLi
cd VeLi
Enter fullscreen mode Exit fullscreen mode

Wait for this to complete. You should see Expo create a new folder called VeLi with some starter files inside.

Now let's install the navigation libraries. These are the special tools that will let us create tabs and navigate between screens:

npm install @react-navigation/native @react-navigation/bottom-tabs
npx expo install react-native-screens react-native-safe-area-context
Enter fullscreen mode Exit fullscreen mode

This installation might take a minute or two. When it finishes, you will have all the navigation power you need built into your project.

Part 2: Creating Your Project Structure

Now let's organize our project properly. Think of this like organizing a filing cabinet—everything has its place, making it easy to find what you need later. Inside your VeLi folder, create these new folders:

VeLi/
├── screens/
├── data/
└── assets/
    └── foods/
Enter fullscreen mode Exit fullscreen mode

You can create folders either through your code editor (VS Code has a "New Folder" option when you right-click in the file explorer) or through your terminal using these commands:

mkdir screens
mkdir data
mkdir -p assets/foods
Enter fullscreen mode Exit fullscreen mode

The mkdir -p command creates nested folders in one go, which is why we can create both assets and the foods folder inside it with a single command.

Part 3: Creating Your Data File

Before we build anything visual, we need data to display. Think of this as stocking your kitchen before you start cooking. Create a file called index.js inside your data folder and add this content:

// data/index.js

// This array holds all our vegetarian food items
// Each food is an object with properties that describe it
export const vegetarianFoods = [
  { 
    id: 1, 
    name: "Avocado Salad", 
    primaryColor: "#2E7D32", // deep avocado green 
    type: "Salad", 
    image: require("./assets/foods/avocado.png") 
  },
  { 
    id: 2, 
    name: "Vegan Burger", 
   primaryColor: "#e71414ff", // rich tomato red // earthy brown (bun-like, grounded) 
    type: "Main Dish", 
    image: require("./assets/foods/burger.png") 
  },
  { 
    id: 3, 
    name: "Quinoa Bowl", 
    primaryColor: "#1B5E20", // dark leafy green 
    type: "Bowl", 
    image: require("./assets/foods/quinoa.png") 
  },
  { 
    id: 4, 
    name: "Veggie Pizza", 
    primaryColor: "#e71414ff", // rich tomato red 
    type: "Pizza", 
    image: require("./assets/foods/pizza.png") 
  },
  { 
    id: 5, 
    name: "Green Smoothie", 
    primaryColor: "#33691E", // forest green 
    type: "Drink", 
    image: require("./assets/foods/smoothie.png") 
  },
];

Enter fullscreen mode Exit fullscreen mode

Get this images from the assets folder in my github repo: iws technical
image files. Each food has a unique color that we chose to match its natural appearance—greens for vegetables, orange for fruits. This creates visual harmony and helps users quickly recognize food categories.

Part 4: Understanding How Navigation Works

Before we write code, let me explain how React Navigation actually works, because understanding this will make everything else click into place.

Imagine your app as a building with multiple floors. In Stack Navigation (which you already know), moving between screens is like climbing stairs—you go up to a new screen, and to go back, you come down. You can only see one floor at a time.

Tab Navigation is different. Imagine instead that you have a building with three rooms all on the same floor, and you can instantly teleport between them using magic doors at the bottom of the building. Those magic doors are your tabs. Each tab is always there, waiting for you to tap it, and when you do, you instantly jump to that room (screen). Nothing is stacked—everything exists side by side.

React Navigation needs two main pieces to work:

The Navigation Container: This is like the building itself. It wraps everything and keeps track of where you are. Every navigation setup must start with this container wrapping your navigators.

The Tab Navigator: This is like the floor plan that defines your three rooms and the magic doors to access them. It knows which screens exist and how to display the tabs at the bottom.

Part 5: Building Your First Simple Screen

Let's start with something super simple so you can see results immediately. Create a file called HomeScreen.js inside your screens folder:

Home Screen

The flex: 1 property is crucial in React Native. It tells this View to expand and fill all available space. Without it, your View would shrink to just fit its content. The combination of justifyContent: 'center' and alignItems: 'center' creates perfect centering both vertically and horizontally.

Now create two more simple screens. Create FoodsScreen.js:

food screen

And create ProfileScreen.js:

Profile Screen

These three screens are intentionally simple right now. We are building the navigation structure first, then we will come back and add the beautiful content. This is a smart way to develop—get the skeleton working first, then add the flesh.

folder

Part 6: Creating Your Tab Navigator (The Magic Part!)

Now comes the exciting part where everything connects. Open your App.js file (it should already exist in your project root from when Expo created the project) and replace everything in it with this code:

App.js

Let me explain what is happening here line by line, because this is the heart of your navigation system:

First, we import NavigationContainer from React Navigation. This is a special component that wraps your entire navigation structure and manages the navigation state. Without it, none of the navigation would work. Think of it as the foundation of a house—invisible but absolutely essential.

Second, we import createBottomTabNavigator, which is a function that creates a tab navigator object. When we call this function, it returns an object with two components inside: Navigator and Screen. That is why we can then use Tab.Navigator and Tab.Screen—they come from this object we created.

Inside our App component, we wrap everything in NavigationContainer. This is mandatory—React Navigation requires this wrapper to function properly.

Inside the NavigationContainer, we place our Tab.Navigator. This component creates the actual tab bar you will see at the bottom of the screen. It looks at all the Tab.Screen components you put inside it and automatically creates a tab button for each one.

Each Tab.Screen has two required props. The name prop is like an ID—it is how you will refer to this screen when navigating (you will see this in action later). The component prop tells React Navigation which component to render when this tab is active.

Now let's test it! Start your development server:

npx expo start
Enter fullscreen mode Exit fullscreen mode

Press i for iOS simulator, a for Android emulator, or scan the QR code with your phone.

What you should notice immediately is that tapping different tabs instantly switches between screens. There is no sliding animation, no back button needed—just instant switching. This is the power of Tab Navigation. Each screen exists simultaneously, and tapping a tab just changes which one is visible.

Part 7: Adding Icons to Your Tabs

Right now your tabs just show text labels, which is functional but not very beautiful or intuitive. Let's add icons to make your app feel professional. Icons provide instant visual recognition—users can identify tabs at a glance without reading.

Update your App.js to add icons:

// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
// Import icon libraries from Expo
import { Ionicons, MaterialIcons, FontAwesome5 } from '@expo/vector-icons';

import HomeScreen from './screens/HomeScreen';
import FoodsScreen from './screens/FoodsScreen';
import ProfileScreen from './screens/ProfileScreen';

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        // screenOptions is a function that runs for each screen
        // It receives information about the current route
        screenOptions={({ route }) => ({
          // tabBarIcon is a function that returns the icon component
          // It receives focused state, color, and size from React Navigation
          tabBarIcon: ({ focused, color, size }) => {
            // We use different icons based on which tab this is
            if (route.name === 'Home') {
              // For Home, we use a leaf icon - perfect for a vegetarian app!
              // When focused (active), show solid leaf; when not, show outline
              return (
                <Ionicons 
                  name={focused ? 'leaf' : 'leaf-outline'} 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Foods') {
              // Restaurant menu icon for the Foods tab
              return (
                <MaterialIcons 
                  name="restaurant-menu" 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Profile') {
              // User icon for Profile, changes style when focused
              return (
                <FontAwesome5 
                  name={focused ? 'user-alt' : 'user'} 
                  size={size} 
                  color={color} 
                />
              );
            }
          },
        })}
      >
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Foods" component={FoodsScreen} />
        <Tab.Screen name="Profile" component={ProfileScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
Enter fullscreen mode Exit fullscreen mode

Let me explain the screenOptions function because it can seem confusing at first. React Navigation calls this function for each screen in your navigator. It passes you information about that screen (in the route parameter), and you return an object of options for that screen.

The tabBarIcon property inside our options is itself a function. React Navigation calls this function whenever it needs to render a tab icon. It passes you three pieces of information: whether this tab is currently focused (active), what color to use, and what size to make the icon. You then return a React component—in our case, an icon component.

The beauty of this system is that React Navigation automatically handles the color changes and size—you just need to provide the icon. When a tab is active, React Navigation passes a different color (which we will customize in the next step).

The focused parameter is particularly useful. We use it to show different icon styles—solid icons for active tabs, outline icons for inactive tabs. This provides clear visual feedback about where the user currently is in the app.

Save your file and look at your app. You should now see beautiful icons on each tab!

tab icons

Part 8: Customizing Tab Colors and Appearance

Now let's make your tabs match VeLi's brand identity with our green color scheme. We want active tabs to be a vibrant green and inactive tabs to be gray, just like professional apps do. Update your screenOptions:

// App.js
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons, MaterialIcons, FontAwesome5 } from '@expo/vector-icons';

import HomeScreen from './screens/HomeScreen';
import FoodsScreen from './screens/FoodsScreen';
import ProfileScreen from './screens/ProfileScreen';

const Tab = createBottomTabNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            if (route.name === 'Home') {
              return (
                <Ionicons 
                  name={focused ? 'leaf' : 'leaf-outline'} 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Foods') {
              return (
                <MaterialIcons 
                  name="restaurant-menu" 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Profile') {
              return (
                <FontAwesome5 
                  name={focused ? 'user-alt' : 'user'} 
                  size={size} 
                  color={color} 
                />
              );
            }
          },
          // These color settings apply to all tabs
          // Active tab uses bright, vibrant green - catches the eye
          tabBarActiveTintColor: '#4CAF50',
          // Inactive tabs use neutral gray - fades into background
          tabBarInactiveTintColor: '#8E8E93',
          // The tab bar itself has a white background with subtle border
          tabBarStyle: {
            backgroundColor: '#FFFFFF',
            borderTopColor: '#EAEAEA',
            borderTopWidth: 1,
          },
        })}
      >
        <Tab.Screen name="Home" component={HomeScreen} />
        <Tab.Screen name="Foods" component={FoodsScreen} />
        <Tab.Screen name="Profile" component={ProfileScreen} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
Enter fullscreen mode Exit fullscreen mode

The tabBarActiveTintColor and tabBarInactiveTintColor properties control the colors of both the icons and labels. When you set these, React Navigation automatically applies them to the color parameter it passes to your tabBarIcon function. This is why we use color={color} in our icon components—we are passing through the color that React Navigation calculated for us.

The color #4CAF50 is a bright, energetic green that I chose because it evokes growth, health, and nature—perfect for a vegetarian app. It is bright enough to clearly indicate "this tab is active" but not so bright that it feels harsh or aggressive.

The gray #8E8E93 for inactive tabs is Apple's standard system gray. It is carefully calibrated to be visible enough to see but muted enough to recede into the background, naturally drawing attention to the active green tab.

tab highlight

tab highlight

Part 9: Customizing the Header Bar

Right now, each screen has a plain white header at the top. Let's style these headers to match our VeLi brand. We will give them a rich green background that complements our tabs:

// App.js - Update your screenOptions
export default function App() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        // Set Home as the initial screen users see when app opens
        initialRouteName="Home"
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            if (route.name === 'Home') {
              return (
                <Ionicons 
                  name={focused ? 'leaf' : 'leaf-outline'} 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Foods') {
              return (
                <MaterialIcons 
                  name="restaurant-menu" 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Profile') {
              return (
                <FontAwesome5 
                  name={focused ? 'user-alt' : 'user'} 
                  size={size} 
                  color={color} 
                />
              );
            }
          },
          tabBarActiveTintColor: '#4CAF50',
          tabBarInactiveTintColor: '#8E8E93',
          tabBarStyle: {
            backgroundColor: '#FFFFFF',
            borderTopColor: '#EAEAEA',
            borderTopWidth: 1,
          },
          // Header styling applies to the top bar of each screen
          headerStyle: {
            backgroundColor: '#1B5E20', // Deep forest green background
          },
          headerTintColor: '#fff', // White text and icons
          headerTitleStyle: {
            fontWeight: 'bold', // Makes the title stand out
            fontSize: 18,
          },
        })}
      >
        {/* We can customize individual screen titles */}
        <Tab.Screen 
          name="Home" 
          component={HomeScreen}
          options={{ title: 'VeLi' }} // Custom title for Home screen
        />
        <Tab.Screen 
          name="Foods" 
          component={FoodsScreen}
          options={{ title: 'Vegetarian Foods' }}
        />
        <Tab.Screen 
          name="Profile" 
          component={ProfileScreen}
          options={{ title: 'My Profile' }}
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
Enter fullscreen mode Exit fullscreen mode

The headerStyle property controls the appearance of the header container. By setting its background to our deep forest green #1B5E20, we create a strong brand presence at the top of every screen.

The headerTintColor controls the color of text and icons in the header. We set it to white for perfect contrast against the dark green background, ensuring readability.

The options prop on individual Tab.Screen components lets us customize that specific screen. Here we are setting custom titles—the Home screen shows just "VeLi" as the app name, while the other screens show descriptive titles.

The initialRouteName prop tells React Navigation which screen to show first when the app launches. Without this, it would default to the first screen in the list (Home in our case), but it is good practice to be explicit.

home

food

Same for the profile

Part 10: Adding a Notification Badge

Many apps show little red circles with numbers on tabs to indicate notifications or new content. Let's add this professional touch to the Home tab:

// App.js - Update just the Home Tab.Screen
<Tab.Screen 
  name="Home" 
  component={HomeScreen}
  options={{ 
    title: 'VeLi',
    // Badge shows a small red circle with a number
    // This could represent unread messages, new recipes, etc.
    tabBarBadge: 5,
  }}
/>
Enter fullscreen mode Exit fullscreen mode

The tabBarBadge property automatically creates a small red circle with your number inside it, positioned at the top right of the tab icon. This is a standard iOS design pattern that users immediately recognize as "you have 5 things to check here." In a real app, this number would come from your state or database indicating actual new items.

notification

Part 11: Making the Badge Dynamic (Conditional)

Static badges are good for learning, but in real apps, badges should appear and disappear based on actual notifications. Let's make ours dynamic using state:

// App.js
import React, { useState } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons, MaterialIcons, FontAwesome5 } from '@expo/vector-icons';

import HomeScreen from './screens/HomeScreen';
import FoodsScreen from './screens/FoodsScreen';
import ProfileScreen from './screens/ProfileScreen';

const Tab = createBottomTabNavigator();

export default function App() {
  // State to track if we have notifications
  // In a real app, this would connect to your notification system
  const [hasNotifications, setHasNotifications] = useState(true);
  const [notificationCount, setNotificationCount] = useState(5);

  return (
    <NavigationContainer>
      <Tab.Navigator
        initialRouteName="Home"
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            if (route.name === 'Home') {
              return (
                <Ionicons 
                  name={focused ? 'leaf' : 'leaf-outline'} 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Foods') {
              return (
                <MaterialIcons 
                  name="restaurant-menu" 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Profile') {
              return (
                <FontAwesome5 
                  name={focused ? 'user-alt' : 'user'} 
                  size={size} 
                  color={color} 
                />
              );
            }
          },
          tabBarActiveTintColor: '#4CAF50',
          tabBarInactiveTintColor: '#8E8E93',
          tabBarStyle: {
            backgroundColor: '#FFFFFF',
            borderTopColor: '#EAEAEA',
            borderTopWidth: 1,
          },
          headerStyle: {
            backgroundColor: '#1B5E20',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
            fontSize: 18,
          },
        })}
      >
        <Tab.Screen 
          name="Home" 
          component={HomeScreen}
          options={{ 
            title: 'VeLi',
            // Only show badge if hasNotifications is true
            // undefined means no badge at all
            tabBarBadge: hasNotifications ? notificationCount : undefined,
          }}
        />
        <Tab.Screen 
          name="Foods" 
          component={FoodsScreen}
          options={{ title: 'Vegetarian Foods' }}
        />
        <Tab.Screen 
          name="Profile" 
          component={ProfileScreen}
          options={{ title: 'My Profile' }}
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here we introduced React state using the useState hook. The hasNotifications state determines whether to show a badge at all, and notificationCount determines what number to display. The expression hasNotifications ? notificationCount : undefined is a ternary operator—it means "if hasNotifications is true, use notificationCount, otherwise use undefined." When React Navigation receives undefined for tabBarBadge, it simply does not render any badge.

In a production app, you would connect this state to your actual notification system. When new messages arrive, you would call setHasNotifications(true) and setNotificationCount(newCount). When the user views all notifications, you would call setHasNotifications(false).

Part 12: Your Complete Navigation Structure

At this point, you have a fully function tab navigation system! Let me show you the complete, final App.js with all features working together:

// App.js - Complete Version
import React, { useState } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons, MaterialIcons, FontAwesome5 } from '@expo/vector-icons';

import HomeScreen from './screens/HomeScreen';
import FoodsScreen from './screens/FoodsScreen';
import ProfileScreen from './screens/ProfileScreen';

const Tab = createBottomTabNavigator();

export default function App() {
  const [hasNotifications, setHasNotifications] = useState(true);
  const [notificationCount, setNotificationCount] = useState(5);

  return (
    <NavigationContainer>
      <Tab.Navigator
        initialRouteName="Home"
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            if (route.name === 'Home') {
              return (
                <Ionicons 
                  name={focused ? 'leaf' : 'leaf-outline'} 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Foods') {
              return (
                <MaterialIcons 
                  name="restaurant-menu" 
                  size={size} 
                  color={color} 
                />
              );
            } else if (route.name === 'Profile') {
              return (
                <FontAwesome5 
                  name={focused ? 'user-alt' : 'user'} 
                  size={size} 
                  color={color} 
                />
              );
            }
          },
          tabBarActiveTintColor: '#4CAF50',
          tabBarInactiveTintColor: '#8E8E93',
          tabBarStyle: {
            backgroundColor: '#FFFFFF',
            borderTopColor: '#EAEAEA',
            borderTopWidth: 1,
          },
          headerStyle: {
            backgroundColor: '#1B5E20',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
            fontSize: 18,
          },
        })}
      >
        <Tab.Screen 
          name="Home" 
          component={HomeScreen}
          options={{ 
            title: 'VeLi',
            tabBarBadge: hasNotifications ? notificationCount : undefined,
          }}
        />
        <Tab.Screen 
          name="Foods" 
          component={FoodsScreen}
          options={{ title: 'Vegetarian Foods' }}
        />
        <Tab.Screen 
          name="Profile" 
          component={ProfileScreen}
          options={{ title: 'My Profile' }}
        />
      </Tab.Navigator>
    </NavigationContainer>
  );
}
Enter fullscreen mode Exit fullscreen mode

Congratulations! You now have a complete understanding of how Tab Navigation works in React Native. You have built the navigation structure step by step, tested each piece, and seen exactly how everything fits together. In the next tutorial, we will build the beautiful content for each screen—the food cards, profile information, and interactive elements that make VeLi come alive.

Part 13: Building the Beautiful Home Screen with Food Cards

Now that your navigation structure is solid, let's build the actual content that makes VeLi come alive. We'll start with the Home screen, transforming it from simple text into an engaging gallery of vegetarian foods.

Understanding the Home Screen Architecture

The Home screen needs to accomplish several things:

  • Display a welcoming header with the app's mission
  • Show a hero cover image that sets the mood
  • Present food cards that users can tap to view details
  • Pass data between screens when a food is selected

Let's build this step by step.

Step 1: Update HomeScreen.js [Exercise 1]

Replace the entire contents of screens/HomeScreen.js with this code:

// screens/HomeScreen.js
import React from 'react';
import { 
  View, 
  Text, 
  StyleSheet, 
  ScrollView, 
  TouchableOpacity,
  Image,
  StatusBar
} from 'react-native';
import { vegetarianFoods } from '../data';

const HomeScreen = ({ navigation }) => {
  // This function handles when a user taps a food card
  // It navigates to the Foods screen and passes the selected food data
  const handleFoodPress = (food) => {
    navigation.navigate('Foods', { selectedFood: food });
  };

  return (
    <ScrollView style={styles.container}>
      {/* StatusBar controls the top system bar appearance */}
      <StatusBar barStyle="light-content" backgroundColor="#1B5E20" />

      {/* Header section with app mission */}
      <View style={styles.header}>
        <Text style={styles.headerTitle}>Vegetarian Living</Text>
        <Text style={styles.headerSubtitle}>Healthy  Fresh  Plant-Based</Text>
      </View>

      {/* Hero cover image here*/}


      {/* Featured foods section */}
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Featured Dishes</Text>
        {vegetarianFoods.map(food => (
          <TouchableOpacity 
            key={food.id}
            style={[styles.foodCard, { backgroundColor: food.primaryColor }]}
            onPress={() => handleFoodPress(food)}
          >
            <Image source={food.image} style={styles.foodImage} />
            <Text style={styles.foodName}>{food.name}</Text>
            <Text style={styles.foodType}>{food.type}</Text>
          </TouchableOpacity>
        ))}
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FAFAFA', // Very light gray background
  },
  header: {
    padding: 20,
    backgroundColor: '#fff',
    borderBottomWidth: 1,
    borderBottomColor: '#EAEAEA', // Subtle border for depth
  },
  headerTitle: {
    fontSize: 24,
    fontWeight: '600',
    color: '#1B5E20', // Our deep forest green for brand consistency
  },
  headerSubtitle: {
    fontSize: 15,
    color: '#444',
    marginTop: 4,
  },
//WRITE STYLES HERE PLEASE [EXCERCISE]

});

export default HomeScreen;
Enter fullscreen mode Exit fullscreen mode

Key Concepts Explained

ScrollView: Unlike View, ScrollView makes content scrollable when it exceeds screen height. Essential for content-heavy screens.

navigation prop: React Navigation automatically passes this to all screen components. It contains methods like navigate() for moving between screens.

Dynamic backgroundColor: Notice { backgroundColor: food.primaryColor } inside the style array. This applies each food's unique color from our data, creating visual variety.

TouchableOpacity: This wrapper makes any component tappable and adds a subtle fade effect on press—standard iOS interaction pattern.

Step 2: Add the Cover Image

You need to add a cover image to your project. Create the file assets/foods/cover.png. For now, you can:

  • Get the images from the assets in the github repo provided below
  • Resize it to around 800x600px (optional)
  • Save it as cover.png in assets/foods/

Or use this terminal command to create a placeholder:

# This creates an empty file - replace it with a real image
touch assets/foods/cover.png
Enter fullscreen mode Exit fullscreen mode

Step 3: Test the Home Screen

Run your app:

npx expo start
Enter fullscreen mode Exit fullscreen mode

Part 14: Building the Foods Screen with Selection Highlighting

Now let's build the Foods screen that displays all vegetarian options and highlights whichever food the user selected from the Home screen.

Update FoodsScreen.js

Replace screens/FoodsScreen.js with:

// screens/FoodsScreen.js
import React from 'react';
import { 
  View, 
  Text, 
  ScrollView, 
  Image, 
  StyleSheet 
} from 'react-native';
import { vegetarianFoods } from '../data';

const FoodsScreen = ({ route }) => {
  // route.params contains data passed from other screens
  // We use optional chaining (?.) because params might be undefined
  const selectedFood = route.params?.selectedFood;

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>All Vegetarian Foods</Text>
        {/* Only show this text if a food was selected */}
        {selectedFood && (
          <Text style={styles.highlightText}>
            Selected: {selectedFood.name}
          </Text>
        )}
      </View>

      <View style={styles.section}>
        {vegetarianFoods.map(food => (
          <View 
            key={food.id}
            style={[
              styles.foodCard,
              { backgroundColor: food.primaryColor },
              // Add selected style if this food matches the selected one
              selectedFood?.id === food.id && styles.selectedFood
            ]}
          >
            <Image source={food.image} style={styles.foodImage} />
            <Text style={styles.foodName}>{food.name}</Text>
            <Text style={styles.foodType}>{food.type}</Text>
          </View>
        ))}
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  //Write styles here as exercise
});

export default FoodsScreen;
Enter fullscreen mode Exit fullscreen mode

Navigation Data Flow

This demonstrates React Navigation's parameter passing:

  1. Home screen calls navigation.navigate('Foods', { selectedFood: food })
  2. React Navigation passes this data as route.params to FoodsScreen
  3. FoodsScreen reads route.params.selectedFood and highlights that card

The conditional styling selectedFood?.id === food.id && styles.selectedFood applies the selected border only to the matching food card.

Part 15: Building the Profile Screen with Avatar

Now let's create the Profile screen with a user avatar and interactive buttons.

Update ProfileScreen.js

Replace screens/ProfileScreen.js with:

// screens/ProfileScreen.js
import React from 'react';
import { 
  View, 
  Text, 
  TouchableOpacity, 
  StyleSheet,
  Image,
  Alert
} from 'react-native';

const ProfileScreen = ({ navigation }) => {
  return (
    <View style={styles.container}>
      <View style={styles.profileContent}>
        {/* Profile card with avatar */}
        <View style={styles.profileCard}>
          {/* Avatar image */}
          <View style={styles.avatarContainer}>
            <Image 
              source={{ uri: 'https://i.pravatar.cc/150?img=12' }}
              style={styles.avatar}
            />
          </View>

          <Text style={styles.profileName}>Fonyuy Gita</Text>
          <Text style={styles.profileEmail}>gita@veggieapp.com</Text>
          <Text style={styles.profileInfo}>Vegetarian since 2020 🌱</Text>
        </View>

        {/* Settings button */}
        <TouchableOpacity 
          style={styles.button}
          onPress={() => Alert.alert("Settings", "Profile settings pressed!")}
        >
          <Text style={styles.buttonText}>Settings</Text>
        </TouchableOpacity>

        {/* Go to Home button */}
        <TouchableOpacity 
          style={[styles.button, styles.secondaryButton]}
          onPress={() => navigation.navigate('Home')}
        >
          <Text style={[styles.buttonText, styles.secondaryButtonText]}>
            Go to Home
          </Text>
        </TouchableOpacity>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  //write styles
});

export default ProfileScreen;
Enter fullscreen mode Exit fullscreen mode

Design Choices

Avatar URL: We're using pravatar.cc, a service that provides placeholder profile pictures. The ?img=12 parameter selects a specific avatar style.

Button Hierarchy: Notice two button styles:

  • Primary button (Settings): Solid green background, white text—high emphasis
  • Secondary button (Go to Home): White background, green border and text—lower emphasis

This ensures the badge displays correctly based on state.

Part 16: Complete App Testing Flow

Now test the complete user journey:

  1. Launch → See Home screen with badge
  2. Tap Fruit Bowl card → Navigate to Foods screen, see "Selected: Fruit Bowl" with highlighted card
  3. Tap Profile tab → See profile with avatar
  4. Tap "Go to Home" button → Return to Home screen
  5. Tap Foods tab directly → See all foods without any selection

Congratulations! You've built a complete tab navigation app with data flow between screens. You now understand how to structure a React Native app, create tab navigators, pass data between screens, and build beautiful, interactive UIs.

Next Steps: In the upcoming tutorial, we'll add authentication screens (onboarding, login, signup) and introduce Expo Router for file-based navigation.

Top comments (0)