Getting Started with React Native and Expo using Hooks 2024

We live in the world with a vast array of mobile devices, primarily dominated by two platforms: iOS and Android. It’s a two-horse race, and I’m sure we can all agree on that. Building a mobile application for both platforms is not an easy task, though.

For iOS, you write code using Objective-C or Swift, and for Android, you will find yourself using Java or Kotlin. Apart from different programming languages used to create an app that can run on each of the two platforms, the toolchains are entirely different.

Many modern-day developers use a specific tech stack to build web applications: HTML, CSS, and JavaScript. Different frameworks fall under the category commonly known as hybrid frameworks. As such, you can use (almost) one set of source code for developing apps for both iOS and Android platforms.

In recent years, hybrid frameworks have evolved from using web views to use native APIs. The cross-platform approach for developing a mobile application comes with its own pros and cons.

One great option that falls under the umbrella of cross-platform development is React Native. It was developed and used by Facebook, and many others use it as well, including Tesla, Walmart, Uber Eats, Instagram, Discord, Wix, and more. React Native is based on Facebook’s web library ReactJS.

What is this tutorial is about?

React Hooks have been available since the release of React version 16.8.x. In this tutorial, you’re going to receive a quick introduction on how to use them in a React Native app.

These functions allow using React state and a component’s lifecycle methods in a functional component. If you’re familiar with React, you know that the functional component has been called as a functional stateless component since the introduction of classes—but not anymore.

Previously, a class component allowed you to have a local state. Using React Hooks, there is no requirement to refactor a React Native class component into a functional component, only because you want to introduce local state or lifecycle methods in that component.

However, they do not work with classes. React provides a few built-in Hooks such as useState and useEffect. You can also create your own Hooks to re-use, to manage state between different components.

Getting started

To quickly create a React Native app, let’s use a tool called Expo. It’s a managed development toolkit that provides a client to preview and make changes to React Native apps using JavaScript. You don’t need tools such as Xcode or Android Studio to get started.

To generate a new app, open a terminal window and enter the following command to install the command-line tool provided by Expo:

npm install -g expo-cli

The next step is to run the expo init command and choose the default template: blank.

# generate a new app
expo init expo-rnHooks

# make sure to navigate inside the project directory
cd expo-rnHooks

Once the project directory is generated, navigate inside it. The demo you’re going to build requires the use of a navigation pattern between two screens. The first screen is going to display a list of items, and through the second screen, you can add an item to the list. This is a typical stack navigation pattern, and using the react-navigation library, you can add this to your React Native app.

The react-navigation library is a third party library that needs to be installed in a React Native or Expo app separately as a dependency. You can either use npm or yarn, but I’m going to stick with yarn. Each navigational pattern comes as a dependency, too. Since this demo requires only one pattern, let’s install that, as well.

The third library you’re going to install is called react-native-paper, which will provide a collection of custom UI components based on Material Design that you can integrate directly. Go back to the terminal window and execute the following command:

yarn add react-navigation react-navigation-stack react-native-paper @react-native-community/masked-view

React Navigation is made up of some core utilities, which are then used by navigators to create the navigation structure in your app. After the above step, Expo requires you to configure these core utilities as dependencies:

expo install react-navigation react-native-gesture-handler react-native-reanimated react-native-screens react-navigation-stack

That’s all for the setup. Let’s build something.

The entry point of a React Native app

The App.js file in the generated app structure is what initializes the Expo app. In other words, it’s the entry point of the development process. By default, it displays a text message and uses a functional component for that. Open the App.js file, and you’ll get the following screen component file:

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

export default function App() {
  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'
  }
})

Components are the visual elements that you see on the screen in a React Native app. The three major components to look for in the above code snippet are:

  • View
  • Text
  • StyleSheet

A View component is the basic building block in a React Native component file. It maps to fundamental native iOS (UIView) and Android (View) components, hence its name. It puts a container element that supports layout styling with flexbox and other styles using a JavaScript object called StyleSheet. Hence, it can be said that View components are primarily used for styling and the layout of children elements.

The StyleSheet component in React Native provides an API to create styles inside the component file. It takes a JavaScript object as it does above, and returns a new StyleSheet object from it. There are no classes or IDs in React Native like in web development. To create a new style object, you can use the StyleSheet.create() method.

The Text component is in many ways just like the View component, except that it’s specifically available to display text. Also, like the View component, it supports styling.

To see the default app in action, start the development server from the terminal window with expo start. Either using a simulator or a real device (make sure it has an Expo client installed from the app store), you can test the app.

Setting up a stack navigation

The react-navigation-stack library provides a built-in function that returns a React component. This function, createStackNavigator, takes a route configuration object and an options object (which is optional).

The react-navigation library provides a function called createAppContainer that also returns a React component. It takes a React component created by the createStackNavigator as a parameter and is directly exported to App.js to be used as our app’s root component.

To create the first route, you need to create the first screen. Create a new file called ViewNotes.js inside the src/screens directory. This screen is going to be served as the first or home screen of the app. Right now, let’s add some mock components, and later we’ll add UI component to reflect the demo app.

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

function ViewNotes() {
  return (
    <View style={styles.container}>
      <View style={styles.titleContainer}>
        <Text style={styles.title}>You do not have any notes</Text>
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingHorizontal: 10,
    paddingVertical: 20
  },
  titleContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    flex: 1
  },
  title: {
    fontSize: 20
  }
})

export default ViewNotes

Next, create a new file called index.js inside src/navigation/ with the following code snippet:

import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import ViewNotes from '../screens/ViewNotes'

const StackNavigator = createStackNavigator(
  {
    ViewNotes: {
      screen: ViewNotes
    }
  },
  {
    initialRouteName: 'ViewNotes',
    headerMode: 'none'
  }
)

export default createAppContainer(StackNavigator)

In the above code snippet, the parameters such as initialRouteName and headerMode are passed as the optional object properties. The first object contains the route configuration.

To see this in action, open the App.js file, import the navigator created above as well as the PaperProvider component from react-native-paper. This provider is going to wrap the navigator and provides the theme to all the components in the framework:

import React from 'react'
import { Provider as PaperProvider } from 'react-native-paper'
import AppNavigator from './src/navigation'

export default function App() {
  return (
    <PaperProvider>
      <AppNavigator />
    </PaperProvider>
  )
}

Make sure the development server is running. You’re going to get the following output in the Expo client.

Adding the second screen to the stack navigator

To complete the navigation process, let’s set up the other screen with some mock text to display. Inside src/screens/, create another file called AddNotes.js and add the following code snippet:

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

function AddNotes() {
  return (
    <View style={styles.container}>
      <View style={styles.titleContainer}>
        <Text style={styles.title}>Add Notes modal screen</Text>
      </View>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingHorizontal: 10,
    paddingVertical: 20
  },
  titleContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    flex: 1
  },
  title: {
    fontSize: 20
  }
})

export default AddNotes

Open the navigation/index.js file and modify the stack navigator:

import { createAppContainer } from 'react-navigation'
import { createStackNavigator } from 'react-navigation-stack'
import ViewNotes from '../screens/ViewNotes'
import AddNotes from '../screens/AddNotes'

const StackNavigator = createStackNavigator(
  {
    ViewNotes: {
      screen: ViewNotes
    },
    AddNotes: {
      screen: AddNotes
    }
  },
  {
    initialRouteName: 'ViewNotes',
    headerMode: 'none',
    mode: 'modal'
  }
)

export default createAppContainer(StackNavigator)

Do note that in the options object, you can specify a mode property for the stack navigator. The value of this property is used to define a specific way to render styles and transitions. The default value of this property is card for screen transitions in iOS and Android. In the above snippet, add the property with a value of modal.

A modal is like a popup that displays the content but temporarily blocks the interaction from the primary screen, which in this case is the ViewNotes screen. To access the second screen, you still have to add a way to navigate to it.

Adding a Floating Button component

react-native-paper also provides cross-platform components to add to the app. In this section, let’s add a floating button on the ViewNotes screen that can be used to navigate to the AddNotes screen. Import the component from the UI library:

import { Text, FAB } from 'react-native-paper'

Next, modify the return function and a FAB component as well as corresponding styles to position it at the bottom of the screen.

function ViewNotes({ navigation }) {
  return (
    <View style={styles.container}>
      <View style={styles.titleContainer}>
        <Text style={styles.title}>You do not have any notes</Text>
      </View>
      <FAB
        style={styles.fab}
        small
        icon='plus'
        label='Add new note'
        onPress={() => navigation.navigate('AddNotes')}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingHorizontal: 10,
    paddingVertical: 20
  },
  titleContainer: {
    alignItems: 'center',
    justifyContent: 'center',
    flex: 1
  },
  title: {
    fontSize: 20
  },
  fab: {
    position: 'absolute',
    margin: 20,
    right: 0,
    bottom: 10
  }
})

In the Expo client, you’re going to get the following output:

Also, when you click the FAB button, it will navigate you to the AddNotes screen:

This is done by using navigation props from react-navigation. Using navigation.navigate as the value of the button press prop onPress, the app will navigate to the screen with its name passed as the second parameter.

onPress={() => navigation.navigate('AddNotes')}

Adding a custom header component

In this section, let’s build a custom header component that’s reusable for both of the screens currently residing in the app. Inside the directory src/components/, create a new file called Header.js.

Import the following components from react-native and react-native-paper:

import React from 'react'
import { View, StyleSheet } from 'react-native'
import { Appbar, Title } from 'react-native-paper'

The Appbar is a component that displays items in a bar. Each of the items can have an action associated, but for the demo app, you only need it to display a title. Add the following code snippet that consists of the component as well as the corresponding styles. The Header component is going to accept one prop titleText, which is the title of a specific screen.

function Header({ titleText }) {
  return (
    <Appbar.Header style={styles.headerContainer}>
      <View style={styles.container}>
        <Title style={styles.title}>{titleText}</Title>
      </View>
    </Appbar.Header>
  )
}

const styles = StyleSheet.create({
  headerContainer: {
    backgroundColor: '#60DBC5'
  },
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  },
  title: {
    color: '#2E7166'
  }
})

export default Header

Import this component in ViewNotes.js and modify the contents of the component file in order to display the header.

// add the following statement
import Header from '../components/Header'

// modify ViewNotes component
function ViewNotes({ navigation }) {
  return (
    <>
      <Header titleText='Simple Note Taker' />
      <View style={styles.container}>
        <View style={styles.titleContainer}>
          <Text style={styles.title}>You do not have any notes</Text>
        </View>
        <FAB
          style={styles.fab}
          small
          icon='plus'
          label='Add new note'
          onPress={() => navigation.navigate('AddNotes')}
        />
      </View>
    </>
  )
}

The following is going to be the output:

Similarly, modify the AddNotes.js file:

// add the following statement
import Header from '../components/Header'

// modify AddNotes component
function AddNotes() {
  return (
    <>
      <Header titleText='Add a new note' />
      <View style={styles.container}>
        <View style={styles.titleContainer}>
          <Text style={styles.title}>Add Notes modal screen</Text>
        </View>
      </View>
    </>
  )
}

Here’s the output:

Implementing Hooks

To clearly understand how functional components can be leveraged to manage a state’s component, let’s try to go through one of the most basic examples by leveraging one of the few built-in Hooks: useState.

Open the ViewNotes.js file and start by importing useState from the React library:

import React, { useState } from 'react'

Let’s create an array to store and display all the notes. Using the array later as the value to the FlatList component, you can easily render each note. In a functional component, you can define a default state variable as shown below:

function ViewNotes({ navigation }) {
  const [notes, setNotes] = useState([])

  // ...
}

React preserves the state between all the re-rendering that happens. The Hook useState returns a pair of values. In the above snippet, the first one is notes, which holds the current value of an empty array (by default) and the second, setNotes, is a function that lets you update the current value, or in the our case, add items to the array.

To add items to the array, let’s create a helper method called addNotes.

const addNote = note => {
  note.id = notes.length + 1
  setNotes([...notes, note])
}

Adding a FlatList component to render notes

When the array notes is empty, we can display a text message that indicates that there’s no item in the list; otherwise, we can render a FlatList component. To do this, you have to import the component itself first.

The component FlatList is an efficient way to create scrolling data lists in a React Native app. It has a simple API and is more efficient and performant, with a large amount of information to display in comparison to its alternatives.

import { StyleSheet, View, FlatList } from 'react-native'
import { Text, FAB, List } from 'react-native-paper'

Next, modify the JSX of the ViewNotes component. Do take note that when navigating to the AddNotes screen, you have to pass it as a prop. This can be done by passing it as the second parameter to the navigation.navigate function.

return (
  <>
    <Header titleText='Simple Note Taker' />
    <View style={styles.container}>
      {notes.length === 0 ? (
        <View style={styles.titleContainer}>
          <Text style={styles.title}>You do not have any notes</Text>
        </View>
      ) : (
        <FlatList
          data={notes}
          renderItem={({ item }) => (
            <List.Item
              title={item.noteTitle}
              description={item.noteValue}
              descriptionNumberOfLines={1}
              titleStyle={styles.listTitle}
            />
          )}
          keyExtractor={item => item.id.toString()}
        />
      )}
      <FAB
        style={styles.fab}
        small
        icon='plus'
        label='Add new note'
        // add a second parameter object
        onPress={() =>
          navigation.navigate('AddNote', {
            addNote
          })
        }
      />
    </View>
  </>
)

From the above snippet, observe that there are three primary props that a FlatList component requires to display a list of data:

  • data: an array of data used to create a list. Generally, this array is built from multiple objects.
  • renderItem: a function that takes an individual element from the data array and renders it on the UI.
  • keyExtractor: tells the list of data to use the unique identifiers or ID for an individual element.

Also, add the listTitle inside the StyleSheet object:

listTitle: {
  fontSize: 20
}

Using navigation parameters to update the state

Since there are no notes, for now, let’s modify the AddNotes screen to make it functional. This screen is responsible for adding a note to the ViewNotes screen. Start by modifying the existing import statements:

import React, { useState } from 'react'
import { View, StyleSheet } from 'react-native'
import { IconButton, TextInput, FAB } from 'react-native-paper'

Using the Hook useState, the component is going to hold the value of each note’s title and its description as noteTitle and noteValue, respectively.

function AddNote({ navigation }) {
  const [noteTitle, setNoteTitle] = useState('')
  const [noteValue, setNoteValue] = useState('')

  // ...
}

The IconButton component from react-native-paper is going to be used to close the modal. After that, add two input fields using TextInput that are going to take the user value for the title of the note and its description.

Lastly, using a FAB component, the user can submit the form. This component is going to be temporarily disabled if there’s no title provided for the note. This can be done by using the disabled prop.

On clicking this button, the component using navigation props is going to perform two actions simultaneously. It’s going to save the note’s title and its description as well as perform an action to go back to the ViewNotes screen.

Here’s the complete AddNotes code snippet along with corresponding styles.

function AddNote({ navigation }) {
  const [noteTitle, setNoteTitle] = useState('')
  const [noteValue, setNoteValue] = useState('')

  function onSaveNote() {
    navigation.state.params.addNote({ noteTitle, noteValue })
    navigation.goBack()
  }
  return (
    <>
      <Header titleText='Add a new note' />
      <IconButton
        icon='close'
        size={25}
        color='white'
        onPress={() => navigation.goBack()}
        style={styles.iconButton}
      />
      <View style={styles.container}>
        <TextInput
          label='Add Title Here'
          value={noteTitle}
          mode='outlined'
          onChangeText={setNoteTitle}
          style={styles.title}
        />
        <TextInput
          label='Add Note Here'
          value={noteValue}
          onChangeText={setNoteValue}
          mode='flat'
          multiline={true}
          style={styles.text}
          scrollEnabled={true}
          returnKeyType='done'
          blurOnSubmit={true}
        />
        <FAB
          style={styles.fab}
          small
          icon='check'
          disabled={noteTitle == '' ? true : false}
          onPress={() => onSaveNote()}
        />
      </View>
    </>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    paddingHorizontal: 20,
    paddingVertical: 20
  },
  iconButton: {
    backgroundColor: 'rgba(46, 113, 102, 0.8)',
    position: 'absolute',
    right: 0,
    top: 40,
    margin: 10
  },
  title: {
    fontSize: 24,
    marginBottom: 20
  },
  text: {
    height: 300,
    fontSize: 16
  },
  fab: {
    position: 'absolute',
    margin: 20,
    right: 0,
    bottom: 0
  }
})

export default AddNote

And here’s the output you’re going to get when navigating to the AddNotes screen:

Running the app

The demo app is complete and ready to be tested. In the Expo client image below, you can find a demo for adding a note and rendering the note:

Conclusion

If you’re getting started with React Native development, Expo as a toolkit can serve you well on your journey. Instead of digging too much into iOS and Android development setup, which can be overwhelming at the start, I’d recommend the least possible amount of tooling and lean more towards learning the core APIs and fundamentals of React Native.

The way that Expo is being maintained, with added support for web and universal apps, it’s going to be an important part of the journey.

You can checkout the next post in this series to implement React Hooks with Redux state management library here:

If you’d like to receive more React Native tutorials in your inbox, you can sign up for my newsletter here.

Avatar photo

Fritz

Our team has been at the forefront of Artificial Intelligence and Machine Learning research for more than 15 years and we're using our collective intelligence to help others learn, understand and grow using these new technologies in ethical and sustainable ways.

Comments 2 Responses

  1. Zach says:

    After trying to use this guide, I’m running into an issue with the goBack() function when trying to add a note.

    I’m getting the following error:
    navigation.goBack is not a function (it is undefined)

    Do you have any idea what this is about and possibly how to fix it?

    1. Avatar photo Fritz says:

      Hey Zach, maybe someone from the comunity might help.

Leave a Reply

Your email address will not be published. Required fields are marked *