In most advanced cities, people no longer need to physically visit restaurants to order the food they desire. The internet enables them to order food online and have it delivered promptly to their preferred location, whether it's their office or home. With the food ordering app, you can browse through the list of available food menus, place an order, and have it delivered to you without leaving your comfort zone.
This tutorial will walk you through building a food ordering mobile app using React Native and Strapi. The app will feature functionalities for users to browse through a selection of foods, add them to their cart, and manage user authentication and profiles. By the end of this guide, you will have created a mobile food application similar to the one shown in the following image:
Before diving into this tutorial, ensure you have the following prerequisites:
Let’s set up the Strapi backend for the application. If you already have a Strapi project, you can skip to the next section. Otherwise, open your terminal and create a new Strapi project using the command below.
npx create-strapi-app@latest my-project --quickstart
After the installation is complete, the Strapi server will start and open in your browser. Register and log in to your dashboard as shown in the image below.
Next, we'll create collection types for our application, specifically: RegisterUsers
, Product
, and Cart
. Here's how to set them up:
Create New Collection Type.
RegisterUsers
and click Continue``.
Refer to the image below for guidance.Now let’s structure the registerusers
collection type by adding the following fields to it:
fullname
: type→textemail
: type→Emailpassword
: type→textcity
: type→textstate
: type→textRepeat the above steps to create the following collection types:
Product Collection:
productname
: type→textprice: type
→textimage: type
→mediaCart Collection: Create a Cart collection type containing the following fields.
productid
: type→textuserid
: type→textAfter creating the collection types, navigate to Settings → Role → Public
and select all operations for each collection type to make them accessible via the API endpoint, as shown in the image below.
Now, navigate to the content manager, click on the "Product
" collection type, and add as many products as you'd like.
With the Strapi backend ready, our next step is to build the mobile application interface. The complete source code for this mobile app is available on GitHub.
To get started, let’s clone the mobile application to your local computer. Open your terminal and execute the following command:
git clone https://github.com/popoolatopzy/StrapiFoodApp
After cloning the application, let’s install all the necessary dependencies. To install the dependencies run the commands below on your terminal.
cd StrapiFoodApp
npm install
The above command installs important dependencies like: React Navigation: This is a popular and widely used library for managing navigation and routing in React Native applications. It provides a structured approach to handling the app's navigation flow, encompassing features such as screen creation, stack navigation, tab navigation, and drawer navigation. React Native Async Storage: A package that provides a straightforward, asynchronous key-value storage system. It is used for caching data, saving user preferences, and restoring session information.
Open the cloned app in your code editor. The app includes four primary screens located in the "components` folder. We'll explore these components for a better understanding of their functionality.
Authentication Screens
Before users can access the food app, they must first register and log in to the application. To create the registration component, navigate to the ‘components
' folder, create a ‘RegisterScreen.js
' file, and add the following code.
1import React, { useState } from "react";
2import { View, Text, TextInput, Button, StyleSheet } from "react-native";
3export default function AboutScreen({ navigation }) {
4 const [email, setEmail] = useState("");
5 const [fullName, setFullName] = useState("");
6 const [password, setPassword] = useState("");
7 const [country, setCountry] = useState("");
8 const [state, setState] = useState("");
9 const handleRegister = async () => {
10 try {
11 // Define the registration data
12 const registrationData = {
13 fullname: fullName,
14 email: email,
15 password: password,
16 city: country,
17 state: state,
18 };
19 // Send a POST request to the registration endpoint
20 const response = await fetch("http://localhost:1337/api/registerusers", {
21 method: "POST",
22 headers: {
23 "Content-Type": "application/json",
24 },
25 body: JSON.stringify({ data: registrationData }),
26 });
27 console.log(response);
28 if (response.ok) {
29 // Registration successful, navigate to the login screen
30 navigation.navigate("Login");
31 } else {
32 // Handle the case where registration is unsuccessful
33 console.error("Registration failed");
34 }
35 } catch (error) {
36 console.error("Error during registration:", error);
37 // Handle the error, e.g., display an error message
38 }
39 };
40 return (
41 <View style={styles.container}>
42 <Text style={styles.title}>Registration</Text>
43 <TextInput
44 style={styles.input}
45 placeholder="Email"
46 onChangeText={(text) => setEmail(text)}
47 value={email}
48 />
49 <TextInput
50 style={styles.input}
51 placeholder="Full Name"
52 onChangeText={(text) => setFullName(text)}
53 value={fullName}
54 />
55 <TextInput
56 style={styles.input}
57 placeholder="Password"
58 onChangeText={(text) => setPassword(text)}
59 value={password}
60 secureTextEntry={true}
61 />
62 <TextInput
63 style={styles.input}
64 placeholder="Country"
65 onChangeText={(text) => setCountry(text)}
66 value={country}
67 />
68 <TextInput
69 style={styles.input}
70 placeholder="State"
71 onChangeText={(text) => setState(text)}
72 value={state}
73 />
74 <Button title="Register" onPress={handleRegister} />
75 </View>
76 );
77}
78const styles = StyleSheet.create({
79});
From the code above, we created a registration form and then sent the user details to our Strapi "registeruser
" endpoint to store the user information. After successful registration, the user is redirected to the login screen.
Next, create a LoginScreen.js
file inside the component
folder. This component is used by the registered user to log into their account using an email
and password
. Add the following code the file.
1import React, { useState } from "react";
2import { View, Text, TextInput, Button, StyleSheet } from "react-native";
3import AsyncStorage from "@react-native-async-storage/async-storage";
4export default function LoginScreen({ navigation }) {
5 const [email, setEmail] = useState("");
6 const [password, setPassword] = useState("");
7 const handleLogin = async () => {
8 try {
9 // Construct the URL with query parameters
10 const baseUrl = "http://localhost:1337/api/registerusers";
11 const queryParams = `?email=${email}&password=${password}`;
12 const url = baseUrl + queryParams;
13 // Send a GET request with the constructed URL
14 const response = await fetch(url, {
15 method: "GET",
16 headers: {
17 "Content-Type": "application/json",
18 },
19 });
20 const data = await response.json();
21 if (data && data.data && data.data.length > 0) {
22 // Assuming the response contains an array
23 for (let i = 0; i < data.data.length; i++) {
24 const user = data.data[i];
25 const userId = user.id; // Get the user ID
26 const email2 = user.attributes.email;
27 const fullname = user.attributes.fullname;
28 const password2 = user.attributes.password;
29 const info = user.attributes;
30 if (email==email2 && password==password2) {
31 const keyValues = [
32 ["userId", userId.toString()],
33 ["email", email2.toString()],
34 ["fullname", fullname.toString()],
35 ];
36 // console.log(fullname)
37 await AsyncStorage.multiSet(keyValues);
38 navigation.navigate("Main");
39 }
40 }
41 } else {
42 }
43 } catch (error) {
44 console.error("Error during registration:", error);
45 }
46 };
47 return (
48 <View style={styles.container}>
49 <Text style={styles.title}>Login</Text>
50 <TextInput
51 style={styles.input}
52 placeholder="Email"
53 onChangeText={(text) => setEmail(text)}
54 value={email}
55 />
56 <TextInput
57 style={styles.input}
58 placeholder="Password"
59 onChangeText={(text) => setPassword(text)}
60 value={password}
61 secureTextEntry={true}
62 />
63 <Button title="Login" onPress={handleLogin} />
64 </View>
65 );
66}
67const styles = StyleSheet.create({
68 container: {
69 flex: 1,
70 justifyContent: "center",
71 alignItems: "center",
72 },
73 title: {
74 fontSize: 24,
75 marginBottom: 20,
76 },
77 input: {
78 width: 300,
79 height: 40,
80 borderColor: "gray",
81 borderWidth: 1,
82 borderRadius: 5,
83 paddingHorizontal: 10,
84 marginBottom: 15,
85 },
86});
From the code above, we created a login form and checked if the user email and password is correct by making a request to the registeruser
endpoint to fetch the user information. After successful registration, the user is redirected to the home screen.
Food home screen All the lists of available products are displayed in the home screen, where users can browse through products, view them and add products to cart. All the products are fetched from the Strapi product endpoint.
Create a HomeScreenn.js
file and add the following code into it:
1import React, { useState, useEffect } from "react";
2import { View, Text, Button, FlatList, StyleSheet, Image, Platform } from "react-native";
3import AsyncStorage from "@react-native-async-storage/async-storage";
4export default function HomeScreen({ navigation }) {
5 const [products, setProducts] = useState([]);
6 useEffect(() => {
7 async function fetchProducts() {
8 try {
9 const response = await fetch("http://localhost:1337/api/products?populate=*");
10 const data = await response.json();
11 if (data && data.data) {
12 const userId = await AsyncStorage.getItem("userId");
13 setProducts(data.data);
14 }
15 } catch (error) {
16 console.error("Error fetching products:", error);
17 }
18 }
19 fetchProducts();
20 }, []);
21 const renderProducts = ({ item }) => {
22 const bb = "http://localhost:1337" + item.attributes.image.data.attributes.formats.small.url;
23 return (
24 <View style={styles.recipeItem} key={item.id}>
25 <Image source={{ uri: bb }} style={styles.recipeImage} />
26 <Text style={styles.recipePrice}>Price: ${item.attributes.price}</Text>
27 <Button
28 title={item.attributes.productname}
29 onPress={() => navigation.navigate("View", { recipe1: item.id})}
30 />
31 </View>
32 );
33 };
34 return (
35 <View style={styles.container}>
36 <FlatList
37 data={products}
38 renderItem={renderProducts}
39 keyExtractor={(item) => item.id.toString()}
40 numColumns={2}
41 />
42 </View>
43 );
44}
45const styles = StyleSheet.create({
46 container: {
47 flex: 1,
48 marginTop: 20,
49 },
50 title: {
51 fontSize: 24,
52 marginBottom: 20,
53 },
54 recipeItem: {
55 width: 150,
56 height: 200,
57 marginBottom: 20,
58 marginLeft: 10,
59 marginRight: 10,
60 borderRadius: 10,
61 backgroundColor: "white",
62 ...Platform.select({
63 ios: {
64 shadowColor: "black",
65 shadowOffset: { width: 0, height: 2 },
66 shadowOpacity: 0.2,
67 shadowRadius: 2,
68 },
69 android: {
70 elevation: 2,
71 },
72 }),
73 alignItems: "center",
74 justifyContent: "center",
75 },
76 recipeImage: {
77 width: 100,
78 height: 100,
79 borderRadius: 100,
80 },
81 recipeName: {
82 fontSize: 18,
83 fontWeight: "bold",
84 marginBottom: 5,
85 },
86 recipePrice: {
87 fontSize: 16,
88 },
89});
From the code above, we make a GET request to the Strapi product endpoint to retrieve and display the list of all available food items. Users can click on a product to view more details about the food.
Now, we will create a Product Preview screen that shows more details about the food the user wants to order, and they can add the food to their cart list. In the components folder, create ViewScreen
and add the following code to it.
1import React, { useEffect, useState } from "react";
2import { View, Text, Image, StyleSheet, Button, TouchableOpacity } from "react-native";
3import { FontAwesome } from "@expo/vector-icons";
4import AsyncStorage from "@react-native-async-storage/async-storage";
5export default function ViewScreen({ route, navigation }) {
6 const { recipe1 } = route.params;
7 const [recipe, setRecipe] = useState(null);
8 useEffect(() => {
9 async function fetchRecipe() {
10 try {
11 const response = await fetch(`http://localhost:1337/api/products/${recipe1}?populate=*`);
12 const data = await response.json();
13 if (data && data.data) {
14 setRecipe(data.data.attributes);
15 }
16 } catch (error) {
17 console.error("Error fetching recipe:", error);
18 }
19 }
20 fetchRecipe();
21 }, [recipe1]);
22 // Function to handle adding the recipe to the cart
23 const handleAddToCart = async () => {
24 console.log(recipe1)
25 if (recipe) {
26 const userId = await AsyncStorage.getItem("userId");
27 const payload = {
28 userid: userId,
29 productid: recipe1, // Adjust this as needed based on your data structure
30 };
31 try {
32 const response = await fetch("http://localhost:1337/api/carts", {
33 method: "POST",
34 headers: {
35 "Content-Type": "application/json",
36 },
37 body: JSON.stringify({ data: payload }),
38 });
39 if (response.ok) {
40 alert(`Added "${recipe.productname}" to the cart`);
41 navigation.navigate("Main");
42 } else {
43 console.error("Error adding to cart. Please try again.");
44 }
45 } catch (error) {
46 console.error("Error adding to cart:", error);
47 }
48 }
49 };
50 return (
51 <View style={styles.container}>
52 {recipe ? (
53 <React.Fragment>
54 <Image source={{ uri: "http://localhost:1337" + recipe.image.data.attributes.formats.small.url }} style={styles.recipeImage} />
55 <Text style={styles.recipeName}>{recipe.productname}</Text>
56 <Text style={styles.recipePrice}>Price: ${recipe.price}</Text>
57 </React.Fragment>
58 ) : (
59 <Text>Loading...</Text>
60 )}
61 {/* Add to Cart Button */}
62 <TouchableOpacity style={styles.addToCartButton} onPress={handleAddToCart}>
63 <FontAwesome name="shopping-cart" size={24} color="white" />
64 <Text style={styles.addToCartButtonText}>Add to Cart</Text>
65 </TouchableOpacity>
66 <Button title="Go Back" onPress={() => navigation.goBack()} />
67 </View>
68 );
69}
70const styles = StyleSheet.create({
71 container: {
72 flex: 1,
73 alignItems: "center",
74 padding: 20,
75 marginTop: 100,
76 },
77 recipeImage: {
78 width: 200,
79 height: 150,
80 marginBottom: 10,
81 borderRadius: 20,
82 },
83 recipeName: {
84 fontSize: 18,
85 fontWeight: "bold",
86 },
87 recipePrice: {
88 fontSize: 16,
89 marginBottom: 10,
90 },
91 addToCartButton: {
92 flexDirection: "row",
93 alignItems: "center",
94 backgroundColor: "#007bff",
95 padding: 10,
96 borderRadius: 5,
97 },
98 addToCartButtonText: {
99 color: "white",
100 fontSize: 18,
101 marginLeft: 10,
102 },
103});
In the code above, we retrieved the product ID of the preview food from the screen URL using React Router and made a request to the Strapi product endpoint to fetch details about the product, passing the product ID along with the request.
Also, the handleAddToCart()
function is used to add a new product to the user's cart list by making a POST request to the cart endpoint.
Now, let's create a Cart Screen where all the foods the users have added to the cart will be displayed. To do that, create a ‘CartScreen.js
' file in the 'component' folder and add the following code to it.
1import React, { useState, useEffect } from "react";
2import { View, Text, Image, StyleSheet } from "react-native";
3import AsyncStorage from "@react-native-async-storage/async-storage";
4export default function CartScreen({ navigation }) {
5 const [cartItems, setCartItems] = useState([]);
6 const [cart, setCart] = useState([]);
7 const [user, setUser] = useState();
8 const [cartList, setCartList] = useState([]);
9 const fetchCartData = async () => {
10 try {
11 const response = await fetch("http://localhost:1337/api/carts");
12 const data = await response.json();
13 if (data && data.data) {
14 const userId = await AsyncStorage.getItem("userId");
15 setUser(userId);
16 setCart(data.data);
17 }
18 } catch (error) {
19 console.error("Error fetching cart items:", error);
20 }
21 };
22 const fetchCartItems = async () => {
23 try {
24 const response = await fetch("http://localhost:1337/api/products?populate=*");
25 const data = await response.json();
26 if (data && data.data) {
27 setCartItems(data.data);
28 }
29 } catch (error) {
30 console.error("Error fetching cart items:", error);
31 }
32 };
33 useEffect(() => {
34 const fetchData = async () => {
35 await fetchCartData();
36 await fetchCartItems();
37 const updatedCartList = [];
38 for (let i = 0; i < cart.length; i++) {
39 if (cart[i].attributes.userid == user) {
40 for (let a = 0; a < cartItems.length; a++) {
41 if (cart[i].attributes.productid == cartItems[a].id) {
42 updatedCartList.push(cartItems[a]);
43 }
44 }
45 }
46 }
47 setCartList(updatedCartList);
48 };
49 fetchData();
50 }, [cart]);
51 const calculateTotalPrice = () => {
52 let totalPrice = 0;
53 for (const item of cartList) {
54 totalPrice += parseFloat(item.attributes.price);
55 }
56 return totalPrice.toFixed(2);
57 };
58 return (
59 <View style={styles.container}>
60 <Text style={styles.title}>Cart</Text>
61 {cartList.map((item) => (
62 <View style={styles.cartItem} key={item.id}>
63 <View style={styles.horizontalContainer}>
64 <Image source={{ uri: "http://localhost:1337" + item.attributes.image.data.attributes.formats.small.url }} style={styles.itemImage} />
65 <View style={styles.productDetails}>
66 <Text style={styles.itemName}>{item.attributes.productname}</Text>
67 <Text style={styles.itemPrice}>Price: ${item.attributes.price}</Text>
68 </View>
69 </View>
70 </View>
71 ))}
72 <Text style={styles.totalPrice}>Total Price: ${calculateTotalPrice()}</Text>
73 </View>
74 );
75}
76const styles = StyleSheet.create({
77 container: {
78 flex: 1,
79 padding: 20,
80 },
81 title: {
82 fontSize: 24,
83 fontWeight: "bold",
84 marginBottom: 20,
85 },
86 cartItem: {
87 backgroundColor: "white",
88 borderRadius: 5,
89 padding: 7,
90 marginBottom: 10,
91 },
92 horizontalContainer: {
93 flexDirection: "row",
94 },
95 itemImage: {
96 width: 100,
97 height: 100,
98 borderRadius: 5,
99 },
100 productDetails: {
101 marginLeft: 10,
102 },
103 itemName: {
104 fontSize: 18,
105 fontWeight: "bold",
106 },
107 itemPrice: {
108 fontSize: 16,
109 },
110 totalPrice: {
111 fontSize: 18,
112 fontWeight: "bold",
113 marginTop: 10,
114 },
115});
In the code above, we fetch the list of all the food that a user has added to the cart and then calculate and display the total price for all the food in the cart.
To create a profile screen where details about the logged-in user are displayed with a logout button, inside the component folder, create a ProfileScreen.js
file and add the following code to it.
1import React, { useState, useEffect } from "react";
2import { View, Text, Image, Button, StyleSheet } from "react-native";
3import AsyncStorage from "@react-native-async-storage/async-storage";
4export default function ProfileScreen({ navigation }) {
5 const [username, setusername] = useState();
6 const [email, setemail] = useState();
7 useEffect(() => {
8 async function info() {
9 try {
10 setusername(await AsyncStorage.getItem("fullname"));
11 setemail(await AsyncStorage.getItem("email"));
12 } catch (error) {
13 console.error("Error fetching cart items:", error);
14 }
15 }
16 info()
17 }, []);
18 return (
19 <View style={styles.container}>
20 <Image source={require("../assets/splash2.png")} style={styles.profileImage} />
21 <Text style={styles.name}>{username}</Text>
22 <Text style={styles.email}>{email}</Text>
23 {/* Add more user details as needed */}
24 <Button title="Edit Profile" onPress={() => navigation.navigate("EditProfile")} />
25 <Button title="Logout" onPress={() => navigation.navigate("Login")} />
26 </View>
27 );
28}
29const styles = StyleSheet.create({
30 container: {
31 flex: 1,
32 alignItems: "center",
33 justifyContent: "center",
34 padding: 20,
35 },
36 profileImage: {
37 width: 150,
38 height: 150,
39 borderRadius: 75, // Half of the width and height to make it circular
40 marginBottom: 20,
41 },
42 name: {
43 fontSize: 24,
44 fontWeight: "bold",
45 marginBottom: 10,
46 },
47 email: {
48 fontSize: 16,
49 marginBottom: 20,
50 },
51});
In the code above, user information is retrieved from local storage and displayed on the profile screen, while the ‘log out
' button ends the user session and redirects them to the login screen.
Now that we have created all the necessary components, let's connect all the components together using React Native Navigation. Open the 'app.js'
file from the project's root folder, and add the following code to it:
1import * as React from "react";
2import { NavigationContainer } from "@react-navigation/native";
3import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
4import { createStackNavigator } from "@react-navigation/stack";
5import Icon from "react-native-vector-icons/FontAwesome";
6import HomeScreen from "./components/HomeScreen";
7import ProfileScreen from "./components/ProfileScreen";
8import ViewScreen from "./components/ViewScreen";
9import CartScreen from "./components/CartScreen";
10import LoginScreen from "./components/LoginScreen";
11import RegisterScreen from "./components/RegisterScreen";
12import SplashScreen from "./components/SplashScreen";
13const Tab = createBottomTabNavigator();
14const Stack = createStackNavigator();
15function MainTabNavigator() {
16 return (
17 <Tab.Navigator>
18 <Tab.Screen
19 name="Food App"
20 component={HomeScreen}
21 options={{
22 tabBarIcon: ({ color, size }) => (
23 <Icon name="home" size={size} color={color} />
24 ),
25 }}
26 />
27 <Tab.Screen
28 name="Cart"
29 component={CartScreen}
30 options={{
31 tabBarIcon: ({ color, size }) => (
32 <Icon name="shopping-cart" size={size} color={color} />
33 ),
34 }}
35 />
36 <Tab.Screen
37 name="Profile"
38 component={ProfileScreen}
39 options={{
40 tabBarIcon: ({ color, size }) => (
41 <Icon name="user" size={size} color={color} />
42 ),
43 }}
44 />
45 </Tab.Navigator>
46 );
47}
48function App() {
49 return (
50 <NavigationContainer>
51 <Stack.Navigator headerMode="none">
52 <Stack.Screen name="Splash" component={SplashScreen} />
53 <Stack.Screen name="Login" component={LoginScreen} />
54 <Stack.Screen name="Register" component={RegisterScreen} />
55 <Stack.Screen name="Main" component={MainTabNavigator} />
56 <Stack.Screen name="View" component={ViewScreen} />
57 </Stack.Navigator>
58 </NavigationContainer>
59 );
60}
61export default App;
Next, let’s start the application by running the command below on your terminal.
npx expo start
After starting the application server, follow the prompts to get the application running on your simulator. The application should be up and running, as shown in the image below.
Congratulations, you have successfully created a mobile food application using React Native and Strapi!
Throughout this tutorial, we have successfully walked through the process of creating a food ordering application utilizing React Native and Strapi. For further reference and exploration:
Feel free to dive into these resources for a deeper understanding and to customize your application.