Hello everyone, Welcome To this React Native App Tutorial, where are going to Make a new app from scratch based on an Adobe XD design.

React Native App Example: Camping App Project Feature

App Concept

The React Native Example we will be building is a simple camping app, made up of 3 screens.

Splash Screen: Made up of a quick introduction to the app, a camping illustration and a navigation button so get started.

Home Screen:

  • Made up of multiple components, starting at the top with the Navigation drawer icon.
  • Header, with user profile, including his/her name, his/her profile image and a notification bell icon button.
  • Search area
  • Discover Area with a list of camping sites and destination categories, example fishing and hiking etc
  • Horizontal list of the destination sites, with quick info including a title, location, rating and reviews.

Details Screen: Full destination sites details, with a title and location

  • short/full description expandable by interaction with read more button
  • List of amenities available within the camping destination
  • Finally the price and a booking button

Getting Starting

To get started we need few stuff installed, for this project I will be using Expo cli, but feel free to follow along by using React Native Cli.

Installation

Expo Cli:

npm install -g expo-cli

React Native Cli:

npm install -g react-native-cli

You can find full details of the installation process from the official Docs

Setup a new environment

Setting up a new project is simple and almost similar for both approaches, you get it started by init from a new folder.

expo init AwesomeProject
cd AwesomeProject
npm start # you can also use: expo start

once the process finishes, you project App.js will look something similar to this.

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default class App extends React.Component {
  render(){
    return (
      <View style={styles.container}>
        <Text>Open up App.js to start working on your app!</Text>
      </View>
    );
  }

}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});
React Native App Example: Camping App Project getting started

Project Structure

Usually when I start a new React Naive project, I have a personal Project structure that I like to follow to stay organize.
It’s not the best optimal structuring approach, but It’s simple and can be extended.

Most of the action resides within the src folder.

Straight within the src folder I add a config.js file, where I add config variables I use. Including colorings, API endpoints and authentication, API Keys etc.

For this project I will only be adding colors and base API endpoint (Which I created using NodeJs and ExpressJs).

export default config = {
  colors: {
    accent: "#F72585",
    primary: "#3A0CA3",
    secondary: "#2F0F79"
  },
  baseUrl: "http://192.168.1.21:3000"

}

Connecting to the server from an emulator or physical device

As I have mentioned, the baseUrl, is the API endpoint I have setup with Express and NodeJs. Which is running In the local host.
Since I am using an emulator (Sometimes physical device) to test the App, I will have to include the IP address of my laptop in the network, wether it’s wifi network or anything. This way any device in the network can connect to the API. In our case the emulator.

Besides the config.js, I add a folder named screens where I will add app screens and it’s components.

As an example The Home screen will have a default component and it’s child components like Discover Categories list etc

The final Structure will look like this.

src/

    config.js

    screens/

           Home/

                Home.js

                Components/
 
                           DiscoverList.js

                           DiscoverListItem.js

Beyond the src file, I add all my assets to the assets folder generated by expo on the same level as src.

Setting up react native navigation

The first thing we’ll need to setup before starting to code, is the app navigation.

As we are planning to add 3 screens to the app, and move between them, react native navigation becomes mandatory.

So we will start by the installation and integration.

Installation

npm install @react-navigation/native

We also need to install Expo dependencies for managed projects

expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

Using React native cli, the command will be.

npm install react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context @react-native-community/masked-view

for react Native 0.60, linking is done automatically, if not run

react-native link

If you are on a mac, you need to install  Cocoapods to handle the linking

npx pod-install ios

And finally we will have to include the gesture handler, if not the app may crash. We can do so in our root file, in our case App.js.

import 'react-native-gesture-handler';

Hooking screens into navigation stack

The first let’s import our dependencies.

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

After that, Let’s create placeholder screens to integrate to the navigation stack.

Within the src/screens, create 3 react natvie components, Splash.js, Home.js, Details.js.
Then import them.

// screens
import Home from "./src/screens/Home/Home"
import Splash from "./src/screens/Splash"
import Details from "./src/screens/Details/Details"
const Stack = createStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen options={{headerShown: false}} name="Splash" component={Splash} />
          <Stack.Screen options={{headerShown: false}} name="Home" component={Home} />
          <Stack.Screen options={{headerShown: false}} name="Details" component={Details} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

export default App;

Now we let’s create a Stack Navigation, which the screens will be attached to.
This won’t work by itself, so we need to include it as a child of a Navigation container.
Within the stack navigation, we can setup our screens one by one, each with a name, component and options props.
For this app, I want my components to be full screen without the default header, so an option headerShown: false will be added to take care of that.

And finally, we export the App.

Splash Screen

The first screen we will dive into is the Splash screen, which serves as the star point of our app.

By itself it doesn’t contain complex functionality, such as navigation to the home screen, it will be pure styling.

Since the Splash screen is the starting point of the app, it will have a main role of loading the font’s we will be using in the entire app.

Let’s get started by importing our dependencies.

import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native';
import config from "../config"
import { useFonts, Rubik_700Bold, Rubik_400Regular, Rubik_500Medium } from '@expo-google-fonts/rubik';
import { AppLoading } from 'expo';

From react native we will need basic components such as StatusBar and Text etc, we will also need to import the config.js, which has the primary colors.
Plus the AppLoading from Expo. Which we will use to display and hide the splash screen if the fonts or any assets are not yet loaded.

Finally the fonts them selves which are needed to be installed first.

The Font I will be using is Rubik, by google fonts.

Install custom google fonts

expo install expo-font @expo-google-fonts/rubik

Let’s get back the the import statement, as you might have notice we are importing useFont and multiple variations of the font itself which we will be using for different Text components within the app.

Splash Components

React Native Camping App Splash Screen

Our root component will be simple like this.

export default function Splash({ navigation }) {
  let [fontsLoaded] = useFonts({
    Rubik_700Bold,
    Rubik_400Regular,
    Rubik_500Medium
  });

  if (!fontsLoaded) {
    return <AppLoading />;
  }
  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <Image 
        style={styles.Image}
        source={require("../../assets/SplashImg.png")} />
      <Text style={styles.title}>Find yourself  outside.</Text>
      <Text style={styles.body}>
        Book unique camping experiences on over 300,000 campsites, cabins, RV parks, public parks and more.
      </Text>
      <TouchableOpacity
          style={styles.exploreBtn}
          onPress={()=> navigation.navigate('Home')}
        >
          <Text style={styles.exploreBtnTxt}>Explore Now!</Text>
      </TouchableOpacity>
    </View>
  );
}

let’s try to check whats going on here.

The navigation prop, is passed from the Stack navigation, it includes various functions we can use to navigate, such as navigate() or goBack().

Then we are loading the needed fonts using useFont, which returns a boolean fontsLoaded, indicating whether the given font’s are fully loaded.

NOTICE: You only need to load the font’s once in your app, then use then anywhere. You do not need to import them into every component.

And the next statement checks the loading status and renders AppLoading component.

And finally the render method which renders what will be displayed.

For our screen, we need to include the StatusBar info and how we want to behave, you can remove it if you want.

Next the Image illustration, which is already saved within the assets folder.

The Title and description texts, and finally the Navigation button to navigate to the next screen, in this case the Home screen.

And here you can find the styling details of each component.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: config.colors.primary,
    alignItems: 'center',
    justifyContent: 'center',
  },
  Image: {
    width:274,
    height:274
  },
  title:{
    fontSize:36,
    fontWeight:"bold",
    color:"white",
    width:"70%",
    textAlign:"center",
    fontFamily:"Rubik_700Bold",
    marginBottom:10,
    lineHeight:49
  },
  body: {
    width:"70%",
    color:"white",
    fontSize:14,
    fontFamily:"Rubik_400Regular",
    lineHeight:24
  },
  exploreBtn:{
    width:"70%",
    backgroundColor:config.colors.accent,
    height:50,
    borderRadius:25,
    justifyContent:"center",
    alignItems:"center",
    marginTop:100
  },
  exploreBtnTxt: {
    color:"white",
    fontFamily:"Rubik_400Regular",
  }
});

Home Screen

React Native Camping App Example Home Screen

The home screen will serve as our main app screen, this is where a user lands, after opening the app.

Here a user can view a list of camping sites destinations, and interact with it. Along with few more options.

At the top, the header area, we have listed a drawer navigation button, a quick snippet about the user, his profile photo and name, and a notification bell button.

Bellow we will add a search input area, where a user can execute search queries.

A discover area, where a user can filter the camping site destinations by its category, which is presented as a list of icons and texts related to each category.

And finally the main destinations listing, which is presented as a list as well, which quick overview about each site.

To get started let’s make a new folder within the src/screens folder, and add a Home.js file, with a child folder named Components.

After importing our dependencies, The Home screen will look like this after we finish

export default function App({ navigation}) {
  const [searchQuery, setSearchQuery] = useState("")
  return (
    <View style={styles.container}>
      <StatusBar style="auto" />
      <View  style={{width:"90%"}}>
        <TouchableOpacity>
          <Image style={styles.drawerIc} source={require("../../../assets/drawerMenu.png")} />
        </TouchableOpacity>
      </View>
      <View style={styles.header}>
        <View style={{flexDirection:"row"}}>
          <Image style={styles.headerIc} source={require("../../../assets/profileImg.png")} />
          <View style={styles.headerInfo}>
            <Text style={styles.headerWelcome}>Welcome</Text>
            <Text style={styles.headerProfile}>Youssef El Habchi</Text>
          </View>
        </View>
        <TouchableOpacity>
          <Image style={styles.headerIc} source={require("../../../assets/bell.png")} />
        </TouchableOpacity>
      </View>
      <View style={styles.inputView}>
        <Image style={{width:13, height: 13}} source={require("../../../assets/search.png")} />
        <TextInput 
          onChangeText = {(query) => setSearchQuery(query)}
          style={styles.input}
          placeholder = "Search Here"
          autoCorrect={false}
          underlineColorAndroid='transparent'
          showSoftInputOnFocus={false}
        />
      </View>
      <View style={{width:"90%"}}>
        <Text style={styles.discoverTxt}>Discover</Text>
      </View>
      <DiscoverList />
      <View style={styles.destinationsView}>
        <Text style={styles.destinationsTxt}>Popular Destinations</Text>
        <DestinationsList navigation={navigation} />
      </View>
      

    </View>
  );
}

Header Area

This screen will only use one state, for search query from the search input, so we can have that as a useState hook.

const [searchQuery, setSearchQuery] = useState("")

So that we can use on onChangeText to update the query state.

Navigation Drawer

Starting from the top, the first view is the Navigation drawer view, within the header part. Which only consists of an Icon button.

      <View  style={{width:"90%"}}>
        <TouchableOpacity>
          <Image style={styles.drawerIc} source={require("../../../assets/drawerMenu.png")} />
        </TouchableOpacity>
      </View>

Profile info area

The next view within the header, profile info.
Which 3 child parts, the profile image, profile name and welcome message and finally the notification bell.

      <View style={styles.header}>
        <View style={{flexDirection:"row"}}>
          <Image style={styles.headerIc} source={require("../../../assets/profileImg.png")} />
          <View style={styles.headerInfo}>
            <Text style={styles.headerWelcome}>Welcome</Text>
            <Text style={styles.headerProfile}>Youssef El Habchi</Text>
          </View>
        </View>
        <TouchableOpacity>
          <Image style={styles.headerIc} source={require("../../../assets/bell.png")} />
        </TouchableOpacity>
      </View>

Search query Input

Below the header, we get into the search input view, with a simple search icon and TextInput View.
Here within the TextInput, We can update the query state after the users finishes writing the query, we can also add an api call to retrieve the new list, but we wont do so within this article.

<View style={styles.inputView}>
        <Image style={{width:13, height: 13}} source={require("../../../assets/search.png")} />
        <TextInput 
          onChangeText = {(query) => setSearchQuery(query)}
          style={styles.input}
          placeholder = "Search Here"
          autoCorrect={false}
          underlineColorAndroid='transparent'
          showSoftInputOnFocus={false}
        />
      </View>

Discover Area

Now we get into some advanced stuff, The Discover Area.

Which consist of 2 parts, the header, and the categories list.

The title is pretty straight forward, Just a simple textview.

      <View style={{width:"90%"}}>
        <Text style={styles.discoverTxt}>Discover</Text>
      </View>
      <DiscoverList />

Then we add a new Component which we will create next, called <DiscoverList />.
we will get into this later this chapter.

Destinations Area

Next is the Destinations area, which also consist of a title and a list.

      <View style={styles.destinationsView}>
        <Text style={styles.destinationsTxt}>Popular Destinations</Text>
        <DestinationsList navigation={navigation} />
      </View>

Similar to the DiscoverList, we will cover this part later.

And finally the styling.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: config.colors.primary,
    alignItems: 'center',
    paddingTop:40
  },
  drawerIc: {
    width: 35,
    height:35
  },
  headerIc:{
    width:50,
    height:50,
    marginRight:10
  },
  header:{
    flexDirection:"row",
    marginTop:10,
    justifyContent:"space-between",
    width:"90%",
    marginBottom:25
  },
  headerInfo:{
    justifyContent:"center",
  },
  headerWelcome:{
    color:"white",
    fontFamily:"Rubik_400Regular",    
  },
  headerProfile:{
    color:"white",
    fontFamily:"Rubik_700Bold",    
    fontSize: 16
  },
  inputView:{
    backgroundColor:"white",
    width:"90%",
    height:50,
    borderRadius:25,
    justifyContent:"center",
    alignItems:"center",
    flexDirection:"row",
    marginBottom: 25
  },
  input: {
    width:"80%",
    height:45,
    marginLeft:10,
    fontSize:20,
  },
  discoverTxt: {
    fontFamily:"Rubik_700Bold",
    fontWeight:"bold",
    fontSize: 28,
    color:"white",
    marginBottom:20
  },
  destinationsView:{
    backgroundColor:"white",
    borderTopRightRadius: 50,
    borderTopLeftRadius: 50,
    width:"100%",
    flex:1,
  },
  destinationsTxt:{
    fontFamily:"Rubik_500Medium",    
    fontSize: 18,
    color:"#B4ADC6",
    fontWeight:"bold",
    marginVertical:20,
    paddingLeft: 25
  }
})

DiscoverList

We will do so by creating 2 new files DiscoverList.js and DiscoverListItem.js within the components folder next to Home.js.

On the DiscoverList.js we create a new React Component and export it with the name DisoverList after importing all react and react native dependencies.

This component will be different. wince we will be doing an API call using Axios

if you do not already have it, install it using

yarn add axios

we will also be using the useEffect hook.
The final version will look like this.

const DisoverList = () => {
  const [discoverItems, setDiscoverItems] = useState([])

  useEffect(() => {
    fetchData = async() => {
      const destinationListUrl = `${config.baseUrl}/discover`
      let list = []
      try {
        list = await axios.get(destinationListUrl)
      } catch (error) {
        console.log("error", error.message, destinationListUrl)
      }
      setDiscoverItems(list.data)
    }
    fetchData()
    
  },[]);

  const renderItem = ( { item }) =>  {
    return [
      <DiscoverListItem 
        key={item.id+ ''}
        item={item}
        onPress={() => console.log(item.title)}
      />
    ]
  }

  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        horizontal={true}
        data={discoverItems}
        renderItem={renderItem}
        showsHorizontalScrollIndicator={false}
      />
    </SafeAreaView>
  )
}

Starting with the state, lets add discover items.

  const [discoverItems, setDiscoverItems] = useState([])

using the useEffect hook, we can setup our api call to get the items data needed.

  useEffect(() => {
    fetchData = async() => {
      const destinationListUrl = `${config.baseUrl}/discover`
      let list = []
      try {
        list = await axios.get(destinationListUrl)
      } catch (error) {
        console.log("error", error.message, destinationListUrl)
      }
      setDiscoverItems(list.data)
    }
    fetchData()
    
  },[]);

Within useEffect, we will create a a function fetchData, which will take care of all API related call stuff, including error handling and state update.

Finally the useEffect hook, needs a second argument of an empty array, to simulate the onComponentMount() lifecycle method.

Next we will add an item component for our list, which we already created a file for DiscoverListItem.js.

  const renderItem = ( { item }) =>  {
    return [
      <DiscoverListItem 
        key={item.id+ ''}
        item={item}
        onPress={() => console.log(item.title)}
      />
    ]
  }

Finally render method, where we will connect everything together.

return (
    <SafeAreaView style={styles.container}>
      <FlatList
        horizontal={true}
        data={discoverItems}
        renderItem={renderItem}
        showsHorizontalScrollIndicator={false}
      />
    </SafeAreaView>
  )

Here, we are using a Flatlist within a safeAreaSearch Component. With a horizontal prop, data from the state which we will get from the API call, and renderItem

plus the styling which is just a container.

const styles = StyleSheet.create({
  container: {
    height:150,
    marginLeft:20,
  },
});

DiscoverListItem

Now Let’s dive into DiscoverListItem.js

This component is a Simple UI Component with a sole role of rendering the category items, it consists of a title and an Icon. Which we will get from the DiscoverList

const DisoverListItem = ({ item, onPress }) => {
  return (
    <TouchableOpacity onPress={onPress} style={styles.item}>
      <Image 
        source={{uri: `${config.baseUrl}/${item.ic}`}}
        style={styles.image}
        />
      <Text style={styles.title}>{item.title}</Text>
    </TouchableOpacity>
  )
}

Styles

const styles = StyleSheet.create({
  item: {
    width:130,
    height: 105,
    backgroundColor: "white",
    borderRadius: 20,
    justifyContent:"center",
    alignItems:"center",
    marginRight:20
  },
  title: {

  },
  image: {
    width:42,
    height:42
  }
});

Destinations Area

This part of the screen will have a title and a list, listing the destination sites of the app.

Similar to the discover area, It will load the app data from an API endpoint using the React native useEffect hook, and render the example list.

We will get starting by creating 2 files for our components, DestinationsList.js and DestinationsListItem.js

So let’s get started

DestinationsList

The final version of the Component will look like this

const DestinationsList = ({ navigation}) => {
  const [destinationsItems, setDestinationsItems] = useState([])

  useEffect(() => {
    fetchData = async() => {
      const discoverListUrl = `${config.baseUrl}/destinations`
      let list = []
      try {
        list = await axios.get(discoverListUrl)
      } catch (error) {
        console.log("error", error.message, discoverListUrl)
      }
      setDestinationsItems(list.data)
    }
    fetchData()
    
  },[]);


  const renderItem = ( { item }) =>  {
    return [
      <DestinationsListItem
        key={item.id+''} 
        item={item}
        onPress={() => navigation.navigate("Details", { item })}
      />
    ]
  }

  return (
    <FlatList
        style={styles.container}
        horizontal={true}
        data={destinationsItems}
        renderItem={renderItem}
        showsHorizontalScrollIndicator={false}
      />
  )
}

The first element we encounter is the Navigation prop, which we didn’t have in the discover List. This navigation prop, we get from the react navigation, in order to transition from one screen to another. Which we need to do, when a user clicks on a destination item, the app will transit to the details screen.

Next the state of the component, in this case we only need one to save the list of destinations from the api call.

  const [destinationsItems, setDestinationsItems] = useState([])

Similar to the DiscoverList, we will use useEffect to handle the api call, once the component mounted. it has the same setup from the DiscoverList check it out.

  useEffect(() => {
    fetchData = async() => {
      const discoverListUrl = `${config.baseUrl}/destinations`
      let list = []
      try {
        list = await axios.get(discoverListUrl)
      } catch (error) {
        console.log("error", error.message, discoverListUrl)
      }
      setDestinationsItems(list.data)
    }
    fetchData()
    
  },[]);

Next the render item for our list

  const renderItem = ( { item }) =>  {
    return [
      <DestinationsListItem
        key={item.id+''} 
        item={item}
        onPress={() => navigation.navigate("Details", { item })}
      />
    ]
  }

and finally the style and render method

  return (
      <FlatList
          style={styles.container}
          horizontal={true}
          data={destinationsItems}
          renderItem={renderItem}
          showsHorizontalScrollIndicator={false}
        />
    )
const styles = StyleSheet.create({
  container: {
    paddingLeft:10
  }
});

Details Screen

The details screen will have the full details about the selected destination and an option to book.

Starting at the top, we can see a simple header, with a back button and the destination title, plus the destination location.

As you might have noticed, the background of the screen, is the same as the destination photo, which add an elegant look to the details screen.

Below, we have 4 sections of destinations details.

Customer Rating: A star rating system and the total user reviews of the destination.

Details: detailed description of the destination, with an option to load more.

Amenities: The list of amenities, as a list and icons. eg. wifi, shower…

Booking: Booking button with price per night for every destination.

The overall code for the details screen, will look like this.

import React, {useState} from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity, ImageBackground } from 'react-native';
import { ScrollView } from 'react-native-gesture-handler';
import config from "../../config"
import Rating from "../Components/Rating"
import AmenitiesList from "./Components/AmenitiesList"
export default function App({ navigation, route}) {
  const { item } = route.params
  const [readMore, setReadMore] = useState(false)
  return (
    <ImageBackground 
      source={{uri: `${config.baseUrl}/${item.photo}`}}
      style={styles.container}
      resizeMode='cover'
    >
      <View  style={styles.leftSpace}>
        <TouchableOpacity onPress={() => navigation.goBack()} >
          <Image style={styles.drawerIc} source={require("../../../assets/arrowBack.png")} />
        </TouchableOpacity>
      </View>
      <View style={styles.leftSpace}>
        <Text style={styles.title}>{item.title}</Text>
        <Text style={styles.location}>{item.location}</Text>
      </View>
      <ScrollView style={styles.detailsView}>
        <Text style={styles.label}>Customers Rating</Text>
        <View style={styles.reviewView}>
          <Rating rating={item.rating} />
          <Text style={styles.reviewsTxt}>{`${item.reviews} Reviews`}</Text>
        </View>
        <Text style={styles.label}>Details</Text>
        <View style={styles.descriptionView}>
          <Text 
            numberOfLines={readMore ? 0 : 4}
            style={styles.descriptionTxt}
            >
            {item.description}
          </Text>
          <TouchableOpacity onPress={() => setReadMore(!readMore)}>
            <Text style={styles.readMoreTxt}>Read More</Text>
          </TouchableOpacity>
        </View>
        <Text style={styles.label}>Amenities</Text>
        <View style={styles.descriptionView}>
          <AmenitiesList amenities={item.amenities}/>
        </View>
        
      </ScrollView>
      <View style={styles.bookingContainer}>
        <View style={styles.bookingView}>
          <Text style={styles.priceTxt}>{`$${item.price}/night`}</Text>
          <TouchableOpacity style={styles.bookingBtn}>
            <Text style={styles.bookingBtnTxt}>Request To Book</Text>
          </TouchableOpacity>
        </View>
        </View>
    </ImageBackground>
  );
}

The first thing you will notice, is that the Details component, has an ImageBackground, which will add the destination as a background of the screen component.

And works similar to the normal Image component.

<ImageBackground 
      source={{uri: `${config.baseUrl}/${item.photo}`}}
      style={styles.container}
      resizeMode='cover'
    >

Header Area

The header area consists of 3 parts, one back navigation button a title and a location.

<View  style={styles.leftSpace}>
        <TouchableOpacity onPress={() => navigation.goBack()} >
          <Image style={styles.drawerIc} source={require("../../../assets/arrowBack.png")} />
        </TouchableOpacity>
      </View>
      <View style={styles.leftSpace}>
        <Text style={styles.title}>{item.title}</Text>
        <Text style={styles.location}>{item.location}</Text>
      </View>

Details Area

The details area consist of 4 sections, each showing different destination details. Wrapped within a ScrollView

Customer Rating

For the sake of this tutorial, we have created an easy React Native custom Rating component Example for our app. The concept is to show a filled, half filled or empty star icon based on the rating of the destination, as follows.

import React from 'react';
import {View, Image, StyleSheet } from 'react-native';

const Rating = ({rating}) => {
  const starFilled = 'https://reactnativemaster.com/wp-content/uploads/2020/09/starFiller.png';
  const starEMpty = 'https://reactnativemaster.com/wp-content/uploads/2020/09/starEmpty.png';
  const starHalfEmpty = 'https://reactnativemaster.com/wp-content/uploads/2020/09/starHalfEmpty.png';

  let ratingStars = []
  

  // add filled stars
  for (let i = 0; i < parseInt(rating); i++) {
    ratingStars.push(
      <Image
        style={styles.starImage}
        source={{ uri: starFilled }}
        key= {new Date().getTime() + Math.random()+""}
    />)
  }
  // add half empty star
  if ( rating % 1 != 0) {
    ratingStars.push(
      <Image
        style={styles.starImage}
        source={{ uri: starHalfEmpty }}
        key= {new Date().getTime() + Math.random()+""}

    />)
  }
  // add empty stars
  for (let i = 0; i < parseInt(5 - rating); i++) {
    ratingStars.push(
      <Image
        style={styles.starImage}
        source={{ uri: starEMpty }}
        key= {new Date().getTime() + Math.random()+""}

    />)
    
  }

  return (
    <View
      style={styles.container}>
        {
          ratingStars
        }
    </View>
  )
}

const styles = StyleSheet.create({
  container:{
    flexDirection: "row",
    marginVertical: 15,
  },
  starImage: {
    width: 15,
    height: 15
  }
})
export default Rating;

And a customer reviews, which is just a simple text.

        <Text style={styles.label}>Customers Rating</Text>
        <View style={styles.reviewView}>
          <Rating rating={item.rating} />
          <Text style={styles.reviewsTxt}>{`${item.reviews} Reviews`}</Text>
        </View>

Details Area

Here we can see the full description of the destination with an option to show full details or a short description.

Fir this app, we are going to create a read more example state and control the numberOfLines of the React Native textView.

At the top of the component add the readMore State.

  const [readMore, setReadMore] = useState(false)

And use it as follows.

<Text style={styles.label}>Details</Text>
        <View style={styles.descriptionView}>
          <Text 
            numberOfLines={readMore ? 0 : 4}
            style={styles.descriptionTxt}
            >
            {item.description}
          </Text>
          <TouchableOpacity onPress={() => setReadMore(!readMore)}>
            <Text style={styles.readMoreTxt}>Read More</Text>
          </TouchableOpacity>
        </View>

Amenities Area

This section renders the list of amenities available in every destination.

The Amenities component consist of AmenitiesList.js and AmenitiesListItem.js.

Which are listview component and its render Item. Similar to the discoverList on the home screen. It renders the icon of and title of the amenities.

import React, { useState, useEffect } from "react";
import { FlatList, SafeAreaView, StyleSheet } from "react-native";
import DiscoverListItem from "./AmenitiesListItem"
import axios from "axios"
import config from "../../../config"


const AmenitiesList = ({ amenities }) => {


  const renderItem = ( { item }) =>  {
    return [
      <DiscoverListItem 
        key={item}
        item={item}
      />
    ]
  }

  return (
    <FlatList
        numColumns={3}
        data={amenities}
        renderItem={renderItem}
        showsHorizontalScrollIndicator={false}
        scrollEnabled={false}
      />
  )
}



export default AmenitiesList
import React, { useState } from "react";
import { TouchableOpacity, Text, StyleSheet, Image } from "react-native";
import config from "../../../config"
const AmenitiesList = ({ item, onPress }) => {
  return (
    <TouchableOpacity onPress={onPress} style={styles.item}>
      <Image 
        resizeMode="contain"
        source={{uri: `${config.baseUrl}/images/amenities/${item}.png`}}
        style={styles.image}
        />
      <Text style={styles.label}>{item}</Text>
    </TouchableOpacity>
  )
}

const styles = StyleSheet.create({
  item: {
    width:91,
    height: 80,
    backgroundColor: "#F5F2FA",
    borderRadius: 20,
    justifyContent:"center",
    alignItems:"center",
    marginRight:10,
    marginBottom:10
  },
  image: {
    width:30,
    height:22
  },
  label: {
    color: "#8F87A5",
    fontFamily: "Rubik_400Regular",
    textAlign:"center",
    fontSize:14,
    paddingTop: 10
  }
});
export default AmenitiesList

Booking Area

The booking area consists of two section, a price per night display for each destination and a booking button.
The entire section has a box shadow, which adds an elegant look to the area.

<View style={styles.bookingContainer}>
        <View style={styles.bookingView}>
          <Text style={styles.priceTxt}>{`$${item.price}/night`}</Text>
          <TouchableOpacity style={styles.bookingBtn}>
            <Text style={styles.bookingBtnTxt}>Request To Book</Text>
          </TouchableOpacity>
        </View>
      </View>

And finally The styling.


const styles = StyleSheet.create({
  container: {
    flex: 1,
    width:"100%",
    height:"100%",
    paddingTop:40,

  },
  leftSpace: {
    marginBottom: 15,
    paddingLeft: 20
  },
  backIc: {
    width: 35,
    height:35
  },
  title: {
    fontSize:36,
    fontWeight:"bold",
    color:"white",
    textAlign:"left",
    fontFamily:"Rubik_700Bold",
    marginBottom:10,
    lineHeight:49
  },
  location: {
    color:"#B4ADC6",
    fontFamily: "Rubik_400Regular",
    textAlign:"left",
    fontSize:16,
    paddingHorizontal: 10
  },
  detailsView: {
    backgroundColor:"white",
    borderTopRightRadius: 50,
    borderTopLeftRadius: 50,
    width:"100%",
    flex:1,
    paddingLeft:25,
    marginTop: 20,
    paddingTop: 10
  },
  label: {
    fontFamily:"Rubik_500Medium",    
    fontSize: 16,
    color:"#B4ADC6",
    fontWeight:"bold",
    paddingLeft: 25,
    paddingVertical:10
  },
  reviewView: {
    flexDirection: "row",
    alignItems:"center",
    paddingLeft: 25,
    marginBottom:20,
    height: 20
  },
  descriptionView: {
    paddingLeft: 25,
    marginBottom:20,
  },
  reviewsTxt: {
    color:"#B4ADC6",
    fontFamily: "Rubik_400Regular",
    textAlign:"left",
    fontSize:14,
    paddingLeft: 10
  },
  descriptionTxt:{
    color: "#4E4466",
    fontFamily: "Rubik_400Regular",
    textAlign:"left",
    fontSize:14,
    textAlign: "left",
    paddingRight:20,
    lineHeight: 25,
    marginBottom: 10
  },
  readMoreTxt: {
    color: config.colors.accent,
  },
  bookingContainer:{
    backgroundColor:"white",
    width:"100%",
    height:100,
  },
  bookingView: {
    justifyContent:"center",
    alignItems:"center",
    flexDirection:"row",
    height:100,
    backgroundColor:"white",
    borderTopRightRadius: 50,
    borderTopLeftRadius: 50,
    shadowColor: "#4E4466",
    shadowOffset: {
      width: 0,
      height: -5,
    },
    shadowOpacity: .1,
    shadowRadius: 20,
    elevation: 8,
  },
  priceTxt: {
    color:"#8F87A5",
    fontFamily: "Rubik_400Regular",
    fontSize:17,
  },
  bookingBtn: {
    backgroundColor:config.colors.accent,
    borderRadius: 30,
    padding: 15,
    marginLeft: 10
  },
  bookingBtnTxt:{
    color: "white",
    fontFamily:"Rubik_500Medium",    
    fontSize: 14,
  }
})

Conclusion

That was it for this React Native App Tutorial, I hope you enjoyed every bit of it and found it as informative as you have expected.

This project will be available on github and Expo.io

Frontend, backend and expo.io
Feel free to comment out your questions and concerns don’t be shy.

Thank your for reading my article and happy coding.