How I Built a UberEats Clone in Under 3 Hours with React Native
React Native is a powerful tool for building native mobile apps using React. In this blog post, I will show you how to build the core functionality of an UberEats clone app in under 3 hours by following 10 steps.
UberEats is a popular food delivery app that allows users to browse restaurant listings, view menus, add items to a cart, and checkout with delivery details. The core features we will build include:
Home screen to display available restaurants
Restaurant listing detail screen
Cart screen
Checkout screen
We will use React Native to build a basic version that focuses on the key workflows rather than advanced features or stylings. React Native allows building truly native iOS and Android apps using React and JavaScript. This makes development much faster compared to building separately for each platform. If you want to learn more visit: https://zipprr.com/ubereats-clone/
Prerequisites
To follow along, you will need:
Node.js 12+
React Native CLI
An iOS or Android mobile emulator
Some React Native experience will be helpful but is not required. We will be using Expo CLI to simplify the setup process and avoid native code integration.
Step 1: Setup Project
Let's initialize our project:
expo init UberEatsClone
cd UberEatsClone
This creates a default React Native project folder structure. We'll also install react-navigation for routing:
npm install @react-navigation/native
Open App.js
and import the stack navigator:
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
Define initial route configurations:
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
This sets up the navigation framework.
Step 2: UI Components
Now let's build some reusable UI components:
Restaurant Listing
function RestaurantListing({restaurant}) {
return (
<View style={styles.listing}>
<Image source={{uri: restaurant.imageUrl}}/>
<Text>{restaurant.name}</Text>
</View>
);
}
HomeScreen
function HomeScreen({navigation}) {
const restaurants = [
// mocked data
];
return (
<View>
<Text>Nearby Restaurants</Text>
<ScrollView>
{restaurants.map(r => (
<RestaurantListing
key={r.id}
restaurant={r}
onPress={() => navigation.navigate('Details')}
/>
))}
</ScrollView>
</View>
);
}
We've defined the UI and connectivity between components.
Step 3: Data Mocks
To start, let's mock some sample data:
// restaurants.json
[{
id: 1,
name: 'Pizza Hut',
imageUrl: '...',
menu: [...]
},
{
id: 2,
name: 'Subway',
imageUrl: '...',
menu: [...]
}]
// menuItems.json
[{
id: 1,
name: 'Pepperoni Pizza',
price: 10,
//...
},
{
id: 2,
name: 'Meatball Sub',
price: 8,
//...
}]
Import and use the mocks:
import restaurants from './data/restaurants.json';
import menuItems from './data/menuItems.json';
function RestaurantListing({restaurant}) {
return (
<View>
<Image source={{uri: restaurant.imageUrl}} />
<Text>{restaurant.name}</Text>
{restaurant.menu.map(item => (
<MenuItem data={menuItems[item]} />
))}
</View>
);
}
Now we have sample data powering our UI.
Step 4: Navigation
Let's add navigation between screens:
// App.js
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
component={HomeScreen}
/>
<Stack.Screen
name="Details"
component={RestaurantDetails}
/>
</Stack.Navigator>
</NavigationContainer>
);
}
// HomeScreen.js
function HomeScreen({navigation}) {
//...
return (
<ScrollView>
{restaurants.map(restaurant => (
<RestaurantListing
key={restaurant.id}
restaurant={restaurant}
onPress={() =>
navigation.navigate('Details', {
restaurant
})
}
/>
))}
</ScrollView>
)
}
Now tapping a listing will navigate to the details screen.
Step 5: Testing the App
To test, run:
expo start
This will launch the Metro bundler and start an iOS/Android simulator.
You should now be able to:
See the list of restaurants on home screen
Tap a restaurant to go to its details screen
The core functionality is working! Of course, more would need to be added like actual ordering.
Step 6: Food Listing Details Screen
Let's build out the item details screen:
function RestaurantDetails({route}) {
const { restaurant } = route.params;
return (
<View>
<Image source={{uri: restaurant.imageUrl}}/>
<Text>{restaurant.name}</Text>
<Text>Menu:</Text>
{restaurant.menu.map(item => (
<MenuItem data={item}/>
))}
<Button
title="Add to Cart"
onPress={() => {}}
/>
</View>
)
}
This displays the selected restaurant data and allows adding items to the cart.
Step 7: Cart Screen
Now let's build out the cart:
// CartScreen.js
function CartScreen({navigation}) {
const [cart, setCart] = useState([]);
const addToCart = (item) => {
setCart([...cart, item]);
}
const removeFromCart = (item) => {
setCart(cart.filter(i => i.id !== item.id));
}
return (
<View>
<Text>Cart ({cart.length})</Text>
{cart.map(item => (
<CartItem
item={item}
onRemove={removeFromCart}
/>
))}
<Button
title="Checkout"
onPress={() => navigation.navigate('Checkout')}
/>
</View>
)
}
Now items can be added/removed and take the user to checkout.
Step 8: Checkout Screen
For checkout:
function CheckoutScreen({route, navigation}) {
const [name, setName] = useState('');
const handleSubmit = () => {
// submit order
navigation.navigate('Home');
}
return (
<View>
<Text>Delivery Details</Text>
<Input
placeholder="Name"
value={name}
onChangeText={setName}
/>
<Button
title="Place Order"
onPress={handleSubmit}
/>
</View>
)
}
Collects delivery info and resets state on order placement.
Step 9: Loading State
Let's add loading states:
const [loading, setLoading] = useState(false);
function RestaurantDetails({route}) {
useEffect(() => {
setLoading(true);
// fetch data
setLoading(false);
}, [])
return (
<View>
{loading ? <ActivityIndicator /> : null}
<Text>Restaurant Name</Text>
// etc
</View>
)
}
Wrap components or show indicator as needed.
Step 10: Error Handling
For errors:
try {
const response = await fetch('/api');
const data = await response.json();
// use data
} catch (err) {
alert('Something went wrong', err);
}
Display failures gracefully.
Conclusion
In under 3 hours we built the core workflows of an UberEats clone app: restaurant listings, item details, cart and checkout.
With React Native, developing truly native mobile experiences is much faster compared to traditional platforms. The code reuse allowed by React makes it very practical for building minimum viable apps quickly.
Of course, this was just a basic clone to demonstrate the key features. There is plenty more that could be added to create a more polished experience:
Authentication and user profiles
Payment integration
Real-time ordering and delivery tracking
Advanced restaurant filters and menus
Push notifications
App performance optimizations
Offline support
Desktop/web versions
Additional screens like search, favorites, orders history
Improved animations, transitions and styling
The project could also integrate with external APIs to retrieve live restaurant and menu data instead of using mocks. Advanced features around maps, locations and orders management could also be implemented.
Overall, React Native proved to be a powerful tool for prototyping a full-fledged clone app very quickly. The modular component structure and navigation architecture provide a solid foundation to continue expanding and improving the app. With more time, it could become production-ready with additional developers working on various pieces.
I hope this blog post gave you a good idea of how to get started with mobile app development using React Native. The framework makes it very approachable even for beginners.
Subscribe to my newsletter
Read articles from prasad venkatachalam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
prasad venkatachalam
prasad venkatachalam
With over 10 years in web and app development, I'm more interested in writing about clone solutions of popular brands