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.

0
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