Build a Todo app with React Native and Expo

2018-10-18

React Native is a framework for building native mobile apps using JavaScript. React Native is based on the same core concepts as ReactJS, giving you, the developer, the power to compose a cross-platform mobile UI by writing JavaScript components.

React Native differs from other hybrid mobile app solutions. It does not use a WebView that renders HTML elements inside an app. It has its own API and by using it, you build mobile apps with native iOS/Android UI components. React Native apps are written in JavaScript. Behind the scenes, React Native is a bridge between JavaScript and other native platform specific components.

In this article, we are going to build a to do application to understand and get hands-on experience with React Native. This mobile application will be cross-platform meaning it will run both on Android and iOS devices. I am going to use Expo for faster development to generate and run the demo in no time. Expo will take care of all the behind the scenes things for us such adding native modules when using vector icons in the demo application. You are only going to focus on the development process for a deeper understanding.

Requirements

To get started you will need three things to follow this article.

  • Node.js & npm
  • Expo CLI

To install expo-cli, please run the following command.

1npm install -g expo-cli

Why use Expo? You should consider using Expo for a React Native application because it handles a lot of hard tasks itself and provides smooth APIs that work with a React Native app outside the box. It is open source and is free to use. It provides a client app and by downloading it from the respective stores based on the mobile platform your device runs, you can easily test applications on real devices.

That said, Expo also has some drawbacks. For example, Expo's API currently does not have support for features like Bluetooth. It works fine with camera, maps, location tracking, analytics, push notifications and so on. Distributing an Expo app is easy too. You can complete the process just by running the command expo publish and it will handle the build process and other tasks by running them behind the scene. It has a dedicated store where you can publish apps for others to use. Quite helpful in prototyping.

Side note: Why not Create-React-Native-App? Just like React, React Native has its own boilerplate that depends on Expo for a faster development process, called create-react-native-app. It works with zero build configuration just like Expo. Recently, the CRNA project has been merged with expo-cli project since both are identical in working.

What We Are Building?

ss1

Getting Started

Write the following command in your terminal to start a project.

1expo init rn-todos-example

When expo's command line interface completes running the package manager, it generates a directory with name you gave like the above command. Open your favorite text editor/IDE and go to a file called App.js. This is what runs the application. You can test the content of the default app generated by running the following command.

1expo-cli start

The below is what you will get in your terminal. It runs the bundler which further triggers the execution of the application. Depending on the OS you are on, you can either use iOS simulator or android emulator to run this application in development mode. The third option is to install the Expo client on your real device and scan the QR code as shown.

ss2

By default, the code in App.js looks like this:

1import React from 'react'
2import { StyleSheet, Text, View } from 'react-native'
3
4export default class App extends React.Component {
5 render() {
6 return (
7 <View style={styles.container}>
8 <Text>Open up App.js to start working on your app!</Text>
9 </View>
10 )
11 }
12}
13
14const styles = StyleSheet.create({
15 container: {
16 flex: 1,
17 backgroundColor: '#fff',
18 alignItems: 'center',
19 justifyContent: 'center'
20 }
21})

Once you run the app in its current state, you will see the following result.

![ss3]https://d2mxuefqeaa7sj.cloudfront.net/s_21CA7D0E2F258DD2425AA73DD44B70100C76B75D0C5466A11D9EB0C2B6E72731_1539260946977_ss3.png

We will replace it with following:

1import React from 'react'
2
3import Main from './app/Main'
4
5export default class App extends React.Component {
6 render() {
7 return <Main />
8 }
9}

Take a look at the directory structure of our demo app.

ss4

In a more complex application, you will find a folder called screens. Since we are using only one screen in the file Main.js you do not have to define it explicitly.

Did you notice the other two directories: utils and components?

Inside the utils directory, I am keeping all the global variables or API calls we need to make. Though in our demo there are no external API calls. I have defined some global variables. Name this file, Colors.js.

1const primaryStart = '#f18a69'
2const primaryEnd = '#d13e60'
3export const primaryGradientArray = [primaryStart, primaryEnd]
4export const lightWhite = '#fcefe9'
5export const inputPlaceholder = '#f1a895'
6export const lighterWhite = '#f4e4e2'
7export const circleInactive = '#ecbfbe'
8export const circleActive = '#90ee90'
9export const itemListText = '#555555'
10export const itemListTextStrike = '#c4c4cc'
11export const deleteIconColor = '#bc2e4c'

It contains all the hex values of colors that we can re-use in many different places of our application. Defining global variables for the purpose of re-using them is a common practice in react native community.

The components directory further contain re-usable components used in our todo application.

Building a Header

To build the header for our application, we need three things: Status bar, background color (we are going to use the same background for the whole screen instead of just header) and header title itself. Let's start with the status bar. Notice the status bar of our application. I am changing it to white such that it will be acceptable once we add a background to our Main screen.

This can be done by importing StatusBar component from react-native. We will be using barStyle prop to change color. For only android devices, you can change the height of the status bar by using currentHeight prop. iOS does not allow this.

For the background, I am going to add a gradient style to our view component. Expo supports this out of the box and you can directly import the component and use it like below.

1import React from 'react'
2import { StyleSheet, Text, View, StatusBar } from 'react-native'
3import { LinearGradient } from 'expo'
4
5import { primaryGradientArray } from './utils/Colors'
6
7export default class Main extends React.Component {
8 render() {
9 return (
10 <LinearGradient colors={primaryGradientArray} style={styles.container}>
11 <StatusBar barStyle="light-content" />;
12 <Text>Open up App.js to start working on your app!</Text>
13 </LinearGradient>
14 )
15 }
16}
17
18const styles = StyleSheet.create({
19 container: {
20 flex: 1
21 }
22})

LinearGradient component is a wrapper over the React Native's View core component. It provides a gradient looking background. It takes at least two values in the array colors as props. We are importing the array from utitls/Colors.js. Next, we create re-usable Header Component inside the components directory.

1import React from 'react'
2import { View, Text, StyleSheet } from 'react-native'
3
4const Header = ({ title }) => (
5 <View style={styles.headerContainer}>
6 <Text style={styles.headerText}>{title.toUpperCase()}</Text>
7 </View>
8)
9
10const styles = StyleSheet.create({
11 headerContainer: {
12 marginTop: 40
13 },
14 headerText: {
15 color: 'white',
16 fontSize: 22,
17 fontWeight: '500'
18 }
19})
20
21export default Header

Import it in Main.js and add a title of your app.

1// after all imports
2import Header from './components/Header'
3
4const headerTitle = 'Todo'
5
6// after status bar, replace the <Text> with
7;<View style={styles.centered}>
8 <Header title={headerTitle} />
9</View>
10
11// add styles
12
13centered: {
14 alignItems: 'center'
15}

Observe that we are passing the title of the app as a prop to Header component. You can definitely use the same component again in the application if needed.

TextInput

In React Native, to record the user input we use TextInput. It uses device keyboard, or in case of a simulator, you can use the hardware keyboard too. It has several configurable props with features such as auto-correction, allow multi-line input, placeholder text, set the limit of characters to be entered, different keyboard styles, etc. For our Todo app, we are going to use several of the features.

1import React from 'react'
2import { StyleSheet, TextInput } from 'react-native'
3
4import { inputPlaceholder } from '../utils/Colors'
5
6const Input = ({ inputValue, onChangeText, onDoneAddItem }) => (
7 <TextInput
8 style={styles.input}
9 value={inputValue}
10 onChangeText={onChangeText}
11 placeholder="Type here to add note."
12 placeholderTextColor={inputPlaceholder}
13 multiline={true}
14 autoCapitalize="sentences"
15 underlineColorAndroid="transparent"
16 selectionColor={'white'}
17 maxLength={30}
18 returnKeyType="done"
19 autoCorrect={false}
20 blurOnSubmit={true}
21 onSubmitEditing={onDoneAddItem}
22 />
23)
24
25const styles = StyleSheet.create({
26 input: {
27 paddingTop: 10,
28 paddingRight: 15,
29 fontSize: 34,
30 color: 'white',
31 fontWeight: '500'
32 }
33})
34
35export default Input

Ignore the props for now that are incoming from its parent component. For a while focus only on the props it has. Let us go through each one of them.

  • value: it is the value of the text input. By default, it will be an empty string since we are using the local state to set it. As the state updates, the value of the text input updates.
  • onChangeText: it is a callback that is called when the text input's text changes. Changed text is passed as an argument to the callback handler.
  • placeholder: just like in HTML, placeholder is to define a default message in the input field indicating as if what is expected.
  • placeholderTextColor: the custom text color of the placeholder string.
  • returnKeyType: determines how the return key on the device's keyboard should look. You can find more values or platform specific values here. Some of the values are specific to each platform.
  • autoCorrect: this prop let us decide whether to show the autocorrect bar along with keyboard or not. In our case, we have set it to false.
  • multiline: if true, the text input can be multiple lines. Like we have set in above.
  • maxlength: helps you define the maximum number of characters that you can allow for the user to enter.
  • autoCapitalize: to automatically capitalize certain characters. We are passing sentences as the default value. This means, every new sentence will automatically have its first character capitalized.
  • underlineColorAndroid: works only with android. It prompts sets a bottom border or an underline.
  • blurOnSubmit: In case of multiline TextInput field, this behaves as when pressing return key, it will blur the field and trigger the onSubmitEditing event instead of inserting a newline into the field.
  • onSubmitEditing: contains the business the logic in form of a callback as to what to do when the return key or input's submit button is pressed. We will be defining this callback in Main.js.

To add this component to Main.js you will have to import it. The props we are passing to the Input component at inputValue are from the state of Main. Other such as onChangeText is a custom method. Define them inside the Main component.

1import React from 'react'
2import { StyleSheet, Text, View, StatusBar } from 'react-native'
3import { LinearGradient } from 'expo'
4
5import { gradientStart, gradientEnd } from './utils/Colors'
6import Header from './components/Header'
7import Input from './components/Input'
8
9const headerTitle = 'Todo'
10
11export default class Main extends React.Component {
12 state = {
13 inputValue: ''
14 }
15
16 newInputValue = value => {
17 this.setState({
18 inputValue: value
19 })
20 }
21
22 render() {
23 const { inputValue } = this.state
24 return (
25 <LinearGradient
26 colors={[gradientStart, gradientEnd]}
27 style={styles.container}
28 >
29 <StatusBar barStyle="light-content" />
30 <View style={styles.centered}>
31 <Header title={headerTitle} />
32 </View>
33 <View style={styles.inputContainer}>
34 <Input inputValue={inputValue} onChangeText={this.newInputValue} />
35 </View>
36 </LinearGradient>
37 )
38 }
39}
40
41const styles = StyleSheet.create({
42 container: {
43 flex: 1
44 },
45 centered: {
46 alignItems: 'center'
47 },
48 inputContainer: {
49 marginTop: 40,
50 paddingLeft: 15
51 }
52})

Building the List Component

To add the value from the Input component and display it on the screen, we are going to use the below code. Create a new file called List.js inside the components directory.

1import React, { Component } from 'react'
2import {
3 View,
4 Text,
5 Dimensions,
6 StyleSheet,
7 TouchableOpacity,
8 Platform
9} from 'react-native'
10import { MaterialIcons } from '@expo/vector-icons'
11
12import {
13 itemListText,
14 itemListTextStrike,
15 circleInactive,
16 circleActive,
17 deleteIconColor
18} from '../utils/Colors'
19
20const { height, width } = Dimensions.get('window')
21
22class List extends Component {
23 onToggleCircle = () => {
24 const { isCompleted, id, completeItem, incompleteItem } = this.props
25 if (isCompleted) {
26 incompleteItem(id)
27 } else {
28 completeItem(id)
29 }
30 }
31
32 render() {
33 const { text, deleteItem, id, isCompleted } = this.props
34
35 return (
36 <View style={styles.container}>
37 <View style={styles.column}>
38 <TouchableOpacity onPress={this.onToggleCircle}>
39 <View
40 style={[
41 styles.circle,
42 isCompleted
43 ? { borderColor: circleActive }
44 : { borderColor: circleInactive }
45 ]}
46 />
47 </TouchableOpacity>
48 <Text
49 style={[
50 styles.text,
51 isCompleted
52 ? {
53 color: itemListTextStrike,
54 textDecorationLine: 'line-through'
55 }
56 : { color: itemListText }
57 ]}
58 >
59 {text}
60 </Text>
61 </View>
62 {isCompleted ? (
63 <View style={styles.button}>
64 <TouchableOpacity onPressOut={() => deleteItem(id)}>
65 <MaterialIcons
66 name="delete-forever"
67 size={24}
68 color={deleteIconColor}
69 />
70 </TouchableOpacity>
71 </View>
72 ) : null}
73 </View>
74 )
75 }
76}
77
78const styles = StyleSheet.create({
79 container: {
80 width: width - 50,
81 flexDirection: 'row',
82 borderRadius: 5,
83 backgroundColor: 'white',
84 height: width / 8,
85 alignItems: 'center',
86 justifyContent: 'space-between',
87 marginVertical: 5,
88 ...Platform.select({
89 ios: {
90 shadowColor: 'rgb(50,50,50)',
91 shadowOpacity: 0.8,
92 shadowRadius: 2,
93 shadowOffset: {
94 height: 2,
95 width: 0
96 }
97 },
98 android: {
99 elevation: 5
100 }
101 })
102 },
103 column: {
104 flexDirection: 'row',
105 alignItems: 'center',
106 width: width / 1.5
107 },
108 text: {
109 fontWeight: '500',
110 fontSize: 16,
111 marginVertical: 15
112 },
113 circle: {
114 width: 30,
115 height: 30,
116 borderRadius: 15,
117 borderWidth: 3,
118 margin: 10
119 },
120 button: {
121 marginRight: 10
122 }
123})
124
125export default List

Our List component uses TouchableOpactiy from react native that behaves like a button but responds to touches on a mobile screen rather than a normal button as we use in web. It also makes use of different colors that we defined earlier. We are also defining a method called toggleCircle that will respond to the onPress action on TouchableOpacity that accordingly respond by checking or unchecking the todo list item.

@expo/vector-icons is provided by Expo to add icons from different libraries such as FontAwesome, IonIcons, MaterialIcons, etc. This is where Expo comes in handy. We do not have to add different libraries as native modules for both mobile OS platforms.

Dimensions is a component that helps us to set the initial width and height of a component before the application runs. We are using its get() method to acquire any device's width and height.

React Native provides an API module called Platform that detects the platform on which the app is running. You can use the detection logic to implement platform-specific code for styling just like we did above or with any other component. To use Platform module, we have to import it from React Native. We are using it to apply styles in the form of shadow that will appear under the every row component when a todo item is being add.

To make this work, we are going to use ScrollView lists and import this component as a child to it in Main.js.

1<View style={styles.list}>
2 <ScrollView contentContainerStyle={styles.scrollableList}>
3 {Object.values(allItems)
4 .reverse()
5 .map(item => (
6 <List
7 key={item.id}
8 {...item}
9 deleteItem={this.deleteItem}
10 completeItem={this.completeItem}
11 incompleteItem={this.incompleteItem}
12 />
13 ))}
14 </ScrollView>
15</View>

ScrollView is a wrapper on View component that provides the user interface for scrollable lists inside a react native app. It is a generic scrolling container that can host multiple other components and views. It works both ways, vertical by default and horizontal by setting the property itself. We will be using this component to display the list of todo items, just after the Input.

To provide styles to it, it uses a prop called contentContainerStyle.

1list: {
2 flex: 1,
3 marginTop: 70,
4 paddingLeft: 15,
5 marginBottom: 10
6 },
7 scrollableList: {
8 marginTop: 15
9 },

Do not worry if you do not understand the rest of the code inside the ScrollView component. Our next step is to add some custom methods and interact with real-time data.

Understanding the AsyncStorage

According to the React Native documentations, AsyncStorage is defined as:

a simple, unencrypted, asynchronous, persistent, key-value storage system that is global to the app. It should be used instead of LocalStorage.

On iOS, AsyncStorage is backed by native code that stores small values in a serialized dictionary and larger values in separate files. On Android, AsyncStorage will use either RocksDB or SQLite based on what is available.

We are now going to define the CRUD operations that will be modified using AsyncStorage such that our application is able to perform these operations with real-time data on the device.

We are going to associate multiple operations for each todo item in the list, such as adding, deleting, editing, etc., basically CRUD operations. We are going to use objects instead of an array to store these items. Operating CRUD operations on an Object is going to be easier in our case. We will be identifying each object through a unique id. In order to generate unique ids we are going to install a module called uuid.

In order to proceed, first we need to run this command:

1npm install
2
3# after it runs successfully,
4npm install --save uuid

The structure of each Todo item is going to be like this:

1232390: {
2 id: 232390, // same id as the object
3 text: 'New item', // name of the todo item
4 isCompleted: false, // by default
5 createdAt: Date.now()
6}

We are going to performing CRUD operations required in our application to work on an object instead of an Array. To read values from an object we are using Object.values(allItems), where allItems is the object that stores all todo list items. We have to add it as an empty object in our local state. This also allows us to map() to traverse each object inside it just like an array. Another thing we have to implement before move on to CRUD operations is to add the new the object of a Todo item when created, should be added at the end of the list. For this we can use reverse() method from JavaScript. This is how our complete Main.js file looks like.

1import React from 'react'
2import {
3 StyleSheet,
4 View,
5 StatusBar,
6 ActivityIndicator,
7 ScrollView,
8 AsyncStorage
9} from 'react-native'
10import { LinearGradient } from 'expo'
11import uuid from 'uuid/v1'
12
13import { primaryGradientArray } from './utils/Colors'
14import Header from './components/Header'
15import SubTitle from './components/SubTitle'
16import Input from './components/Input'
17import List from './components/List'
18import Button from './components/Button'
19
20const headerTitle = 'Todo'
21
22export default class Main extends React.Component {
23 state = {
24 inputValue: '',
25 loadingItems: false,
26 allItems: {},
27 isCompleted: false
28 }
29
30 componentDidMount = () => {
31 this.loadingItems()
32 }
33
34 newInputValue = value => {
35 this.setState({
36 inputValue: value
37 })
38 }
39
40 loadingItems = async () => {
41 try {
42 const allItems = await AsyncStorage.getItem('Todos')
43 this.setState({
44 loadingItems: true,
45 allItems: JSON.parse(allItems) || {}
46 })
47 } catch (err) {
48 console.log(err)
49 }
50 }
51
52 onDoneAddItem = () => {
53 const { inputValue } = this.state
54 if (inputValue !== '') {
55 this.setState(prevState => {
56 const id = uuid()
57 const newItemObject = {
58 [id]: {
59 id,
60 isCompleted: false,
61 text: inputValue,
62 createdAt: Date.now()
63 }
64 }
65 const newState = {
66 ...prevState,
67 inputValue: '',
68 allItems: {
69 ...prevState.allItems,
70 ...newItemObject
71 }
72 }
73 this.saveItems(newState.allItems)
74 return { ...newState }
75 })
76 }
77 }
78
79 deleteItem = id => {
80 this.setState(prevState => {
81 const allItems = prevState.allItems
82 delete allItems[id]
83 const newState = {
84 ...prevState,
85 ...allItems
86 }
87 this.saveItems(newState.allItems)
88 return { ...newState }
89 })
90 }
91
92 completeItem = id => {
93 this.setState(prevState => {
94 const newState = {
95 ...prevState,
96 allItems: {
97 ...prevState.allItems,
98 [id]: {
99 ...prevState.allItems[id],
100 isCompleted: true
101 }
102 }
103 }
104 this.saveItems(newState.allItems)
105 return { ...newState }
106 })
107 }
108
109 incompleteItem = id => {
110 this.setState(prevState => {
111 const newState = {
112 ...prevState,
113 allItems: {
114 ...prevState.allItems,
115 [id]: {
116 ...prevState.allItems[id],
117 isCompleted: false
118 }
119 }
120 }
121 this.saveItems(newState.allItems)
122 return { ...newState }
123 })
124 }
125
126 deleteAllItems = async () => {
127 try {
128 await AsyncStorage.removeItem('Todos')
129 this.setState({ allItems: {} })
130 } catch (err) {
131 console.log(err)
132 }
133 }
134
135 saveItems = newItem => {
136 const saveItem = AsyncStorage.setItem('Todos', JSON.stringify(newItem))
137 }
138
139 render() {
140 const { inputValue, loadingItems, allItems } = this.state
141
142 return (
143 <LinearGradient colors={primaryGradientArray} style={styles.container}>
144 <StatusBar barStyle="light-content" />
145 <View style={styles.centered}>
146 <Header title={headerTitle} />
147 </View>
148 <View style={styles.inputContainer}>
149 <SubTitle subtitle={"What's Next?"} />
150 <Input
151 inputValue={inputValue}
152 onChangeText={this.newInputValue}
153 onDoneAddItem={this.onDoneAddItem}
154 />
155 </View>
156 <View style={styles.list}>
157 <View style={styles.column}>
158 <SubTitle subtitle={'Recent Notes'} />
159 <View style={styles.deleteAllButton}>
160 <Button deleteAllItems={this.deleteAllItems} />
161 </View>
162 </View>
163
164 {loadingItems ? (
165 <ScrollView contentContainerStyle={styles.scrollableList}>
166 {Object.values(allItems)
167 .reverse()
168 .map(item => (
169 <List
170 key={item.id}
171 {...item}
172 deleteItem={this.deleteItem}
173 completeItem={this.completeItem}
174 incompleteItem={this.incompleteItem}
175 />
176 ))}
177 </ScrollView>
178 ) : (
179 <ActivityIndicator size="large" color="white" />
180 )}
181 </View>
182 </LinearGradient>
183 )
184 }
185}
186
187const styles = StyleSheet.create({
188 container: {
189 flex: 1
190 },
191 centered: {
192 alignItems: 'center'
193 },
194 inputContainer: {
195 marginTop: 40,
196 paddingLeft: 15
197 },
198 list: {
199 flex: 1,
200 marginTop: 70,
201 paddingLeft: 15,
202 marginBottom: 10
203 },
204 scrollableList: {
205 marginTop: 15
206 },
207 column: {
208 flexDirection: 'row',
209 alignItems: 'center',
210 justifyContent: 'space-between'
211 },
212 deleteAllButton: {
213 marginRight: 40
214 }
215})

Let us take a look at the custom CRUD methods. onDoneAddItem() starts by invoking this.setState that has access to a prevState object if the input value is not empty. It gives us any Todo Item that has been previously added to our list. Inside its callback, we will first create a new ID using uuid and then create an object called newItemObject which uses the ID as a variable for the name. Then, we create a new object called newState which uses the prevState object, clears the textInput for newTodoItem and finally adds our newItemObject at the end of the other Todo items list. It might sound overwhelming since a lot is going on but try implementing the code, you will understand it better.

To delete an item from the todo list object, we have to get the id of the item from the state. In Main.js we have deleteItem.

1deleteItem = id => {
2 this.setState(prevState => {
3 const allItems = prevState.allItems
4 delete allItems[id]
5 const newState = {
6 ...prevState,
7 ...allItems
8 }
9 this.saveItems(newState.allItems)
10 return { ...newState }
11 })
12}

This is further passed as a prop to our List component as deleteItem={this.deleteItem}. We are adding the id of an individual todo item since we are going to use this id to delete the item from the list.

The completeItem and incompleteItem tracks which item in the todo list have been marked completed by the user and vice versa the process. In AsyncStorage the items are saved in strings. It cannot store objects. So when saving the item if you are not using JSON.stringify() your app is going to crash. Similarly, when fetching the item from the storage, we have to parse it using JSON.parse() like we do above in loadingItems() method.

1const saveTodos = AsyncStorage.setItem('Todos', JSON.stringify(newToDos))

Here, you can say that Todos is the name of the collection. setItem() function from AsyncStorage is similar to any key-value paired database. The first item Todos is the key, and newItem is going to be the value, in our case the todo list items as different objects. I have already described the structure of data we are using to create each todo list item.

To verify that the data is getting saved on the device, we can restart the application. But how is our application fetching the data from device's storage? This is done by an asynchronous function we have defined called loadingItems. Since it is asynchronous, We have to wait till the application is done reading data from the device's storage. Usually, nowadays smartphones do not take much time to perform this action. To run this asynchronous function we use React's life cycle hook componentDidMount which is called immediately after a component is initialized.

1componentDidMount = () => {
2 this.loadingItems()
3}
4
5loadingItems = async () => {
6 try {
7 const allItems = await AsyncStorage.getItem('Todos')
8 this.setState({
9 loadingItems: true,
10 allItems: JSON.parse(allItems) || {}
11 })
12 } catch (err) {
13 console.log(err)
14 }
15}

loadingItems is then used inside a conditional operator which can be defined as if the data is read from the storage, you can render the List component otherwise just render a loading component provided by ActivityIndicator which again comes as a React Native core module.

Lastly, AsyncStorage also provides a function to clear all application data in one touch by executing removeItem() function.

1deleteAllItems = async () => {
2 try {
3 await AsyncStorage.removeItem('Todos')
4 this.setState({ allItems: {} })
5 } catch (err) {
6 console.log(err)
7 }
8}

Running the app

Now that we have connected all of our components, go to the terminal and run the command expo-cli start if the app isn’t already running in the iOS simulator or Android emulator. The start command starts or restarts a local server for your app and gives you a URL or QR code. You can press a for Android emulator or i for iOS simulator.

After you have successfully started the application, you can start playing with it by adding to do items in the WHAT'S NEXT? section. Items successfully added will appear under the heading Recent Notes as shown below.

ss

Conclusion

I leave the SubTitle component for you to customize. It is the same as Header but it is being used twice in our application. Refer to Main.js file to see where it is used.

This completes our tutorial for building a React Native Application from scratch using Expo. You can add more functionality such as updating the list item by making use of the created Date field we added to our data model. The possibilities to enhance this application are endless. For example, you can add another functionality for updating the text of a list item. You can add an icon next to the delete item and then let the user select which item they want to edit.

You now have an in-depth understanding of how things work in React Native and why there is much less difference between React Native and Expo. You can find the complete code for this project here: GitHub.

You can find the complete source code for the above article at Github Repo

Originally published at Pusher

I'm Aman working as an independent fullstack developer with technologies such as Node.js, ReactJS, and React Native. I try to document and write tutorials to help JavaScript, Web and Mobile developers.