React Native

React Native CRUD tutorial

Introduction

Storing data on Parse is built around Parse.Object class. Each Parse.Object contains key-value pairs of JSON-compatible data. This data is schemaless, which means that you don’t need to specify ahead of time what keys exist on each Parse.Object. You can simply set whatever key-value pairs you want, and our backend will store it.

You can also specify the datatypes according to your application needs and persist types such as number, boolean, string, DateTime, list, GeoPointers, and Object, encoding them to JSON before saving. Parse also supports store and query relational data by using the types Pointers and Relations.

In this guide, you will learn how to perform basic data operations through a CRUD example app, which will show you how to create, read, update and delete data from your Parse server database in React Native. You will first create your component functions for each CRUD operation, using them later in a complete screen layout, resulting in a to-do list app.

Prerequisites

To complete this tutorial, you will need:

Goal

To build a basic CRUD application in React Native using Parse.

Step 1 - Creating data objects

The first step to manage your data in your Parse database is to have some on it. Let’s now make a createTodo function that will create a new instance of Parse.Object with the “Todo” subclass. The Todo will have a title(string) describing the task and a done(boolean) field indicating if the task is completed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const createTodo = async function () {
  // This value comes from a state variable
  const newTodoTitleValue = newTodoTitle;
  // Creates a new Todo parse object instance
  let Todo = new Parse.Object('Todo');
  Todo.set('title', newTodoTitleValue);
  Todo.set('done', false);
  // After setting the todo values, save it on the server
  try {
    await Todo.save();
    // Success
    Alert.alert('Success!', 'Todo created!');
    // Refresh todos list to show the new one (you will create this function later)
    readTodos();
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const createTodo = async function (): Promise<boolean> {
  // This value comes from a state variable
  const newTodoTitleValue: string = newTodoTitle;
  // Creates a new Todo parse object instance
  let Todo: Parse.Object = new Parse.Object('Todo');
  Todo.set('title', newTodoTitleValue);
  Todo.set('done', false);
  // After setting the todo values, save it on the server
  try {
    await Todo.save();
    // Success
    Alert.alert('Success!', 'Todo created!');
    // Refresh todos list to show the new one (you will create this function later)
    readTodos();
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};

Notice that if your database does not have a Todo table (or subclass) in it yet, Parse will create it automatically and also add any columns set inside the Parse.Object instance using the Parse.Object.set() method, which takes two arguments: the field name and the value to be set.

Step 2 - Reading data objects

After creating some data in your database, your application can now be able to read it from the server and show it to your user. Go ahead and create a readTodos function, which will perform a Parse.Query, storing the result inside a state variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const readTodos = async function () {
  // Reading parse objects is done by using Parse.Query
  const parseQuery = new Parse.Query('Todo');
  try {
    let todos = await parseQuery.find();
    // Be aware that empty or invalid queries return as an empty array
    // Set results to state variable
    setReadResults(todos);
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const readTodos = async function (): Promise<boolean> {
  // Reading parse objects is done by using Parse.Query
  const parseQuery: Parse.Query = new Parse.Query('Todo');
  try {
    let todos: Parse.Object[] = await parseQuery.find();
    // Be aware that empty or invalid queries return as an empty array
    // Set results to state variable
    setReadResults(todos);
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};

The Parse.Query class is very powefull, many constraints and orderings can be applied to your queries. For now, we will stick to this simple query, which will retrieve every saved Todo object.

Step 3 - Updating data objects

Updating a Parse.Object instance is very similar to creating a new one, except that in this case, you need to assign the previously created objectId to it and then save, after setting your new values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const updateTodo = async function (todoId, done) {
  // Create a new todo parse object instance and set todo id
  let Todo = new Parse.Object('Todo');
  Todo.set('objectId', todoId);
  // Set new done value and save Parse Object changes
  Todo.set('done', done);
  try {
    await Todo.save();
    // Success
    Alert.alert('Success!', 'Todo updated!');
    // Refresh todos list
    readTodos();
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const updateTodo = async function (
  todoId: string,
  done: boolean,
): Promise<boolean> {
  // Create a new todo parse object instance and set todo id
  let Todo: Parse.Object = new Parse.Object('Todo');
  Todo.set('objectId', todoId);
  // Set new done value and save Parse Object changes
  Todo.set('done', done);
  try {
    await Todo.save();
    // Success
    Alert.alert('Success!', 'Todo updated!');
    // Refresh todos list
    readTodos();
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};

Since this example app represents a to-do list, your update function takes an additional argument, the done value, which will represent if the specific task is completed or not.

Step 4 - Deleting data objects

To delete a data object, you need to call the .destroy() method in the Parse.Object instance representing it. Please be careful because this operation is not reversible.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const deleteTodo = async function (todoId) {
  // Create a new todo parse object instance and set todo id
  const Todo = new Parse.Object('Todo');
  Todo.set('objectId', todoId);
  // .destroy should be called to delete a parse object
  try {
    await Todo.destroy();
    Alert.alert('Success!', 'Todo deleted!');
    // Refresh todos list to remove this one
    readTodos();
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const deleteTodo = async function (todoId: string): Promise<boolean> {
  // Create a new todo parse object instance and set todo id
  let Todo: Parse.Object = new Parse.Object('Todo');
  Todo.set('objectId', todoId);
  // .destroy should be called to delete a parse object
  try {
    await Todo.destroy();
    Alert.alert('Success!', 'Todo deleted!');
    // Refresh todos list to remove this one
    readTodos();
    return true;
  } catch (error) {
    // Error can be caused by lack of Internet connection
    Alert.alert('Error!', error.message);
    return false;
  };
};

Let´s now use these four functions in a complete component, so you can test it and make sure that every CRUD operation is working properly.

Step 5 - Using CRUD in a React Native component

Here is the complete component code, including styled user interface elements, state variables, and calls to your CRUD functions. You can create a separate component in a file called TodoList.js/TodoList.tsx containing the following code or add it to your main application file (App.js/App.tsx or index.js):

TodoList.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
import React, {useState} from 'react';
import {
  Alert,
  View,
  SafeAreaView,
  Image,
  ScrollView,
  StatusBar,
  StyleSheet,
  TouchableOpacity,
} from 'react-native';
import Parse from 'parse/react-native';
import {
  List,
  Title,
  IconButton,
  Text as PaperText,
  Button as PaperButton,
  TextInput as PaperTextInput,
} from 'react-native-paper';

export const TodoList = () => {
  // State variables
  const [readResults, setReadResults] = useState([]);
  const [newTodoTitle, setNewTodoTitle] = useState('');

  // Functions used by the screen components
  const createTodo = async function () {
    // This value comes from a state variable
    const newTodoTitleValue = newTodoTitle;
    // Creates a new Todo parse object instance
    let Todo = new Parse.Object('Todo');
    Todo.set('title', newTodoTitleValue);
    Todo.set('done', false);
    // After setting the todo values, save it on the server
    try {
      await Todo.save();
      // Success
      Alert.alert('Success!', 'Todo created!');
      // Refresh todos list to show the new one (you will create this function later)
      readTodos();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const readTodos = async function () {
    // Reading parse objects is done by using Parse.Query
    const parseQuery = new Parse.Query('Todo');
    try {
      let todos = await parseQuery.find();
      // Be aware that empty or invalid queries return as an empty array
      // Set results to state variable
      setReadResults(todos);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const updateTodo = async function (todoId, done) {
    // Create a new todo parse object instance and set todo id
    let Todo = new Parse.Object('Todo');
    Todo.set('objectId', todoId);
    // Set new done value and save Parse Object changes
    Todo.set('done', done);
    try {
      await Todo.save();
      // Success
      Alert.alert('Success!', 'Todo updated!');
      // Refresh todos list
      readTodos();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    };
  };

  const deleteTodo = async function (todoId) {
    // Create a new todo parse object instance and set todo id
    let Todo = new Parse.Object('Todo');
    Todo.set('objectId', todoId);
    // .destroy should be called to delete a parse object
    try {
      await Todo.destroy();
      Alert.alert('Success!', 'Todo deleted!');
      // Refresh todos list to remove this one
      readTodos();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  return (
    <>
      <StatusBar backgroundColor="#208AEC" />
      <SafeAreaView style={Styles.container}>
        <View style={Styles.header}>
          <Image
            style={Styles.header_logo}
            source={ {
              uri:
                'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png',
            } }
          />
          <PaperText style={Styles.header_text_bold}>
            {'React Native on Back4App'}
          </PaperText>
          <PaperText style={Styles.header_text}>{'Product Creation'}</PaperText>
        </View>
        <View style={Styles.wrapper}>
          <View style={Styles.flex_between}>
            <Title>Todo List</Title>
            {/* Todo read (refresh) button */}
            <IconButton
              icon="refresh"
              color={'#208AEC'}
              size={24}
              onPress={() => readTodos()}
            />
          </View>
          <View style={Styles.create_todo_container}>
            {/* Todo create text input */}
            <PaperTextInput
              value={newTodoTitle}
              onChangeText={text => setNewTodoTitle(text)}
              label="New Todo"
              mode="outlined"
              style={Styles.create_todo_input}
            />
            {/* Todo create button */}
            <PaperButton
              onPress={() => createTodo()}
              mode="contained"
              icon="plus"
              color={'#208AEC'}
              style={Styles.create_todo_button}>
              {'Add'}
            </PaperButton>
          </View>
          <ScrollView>
            {/* Todo read results list */}
            {readResults !== null &&
              readResults !== undefined &&
              readResults.map((todo) => (
                <List.Item
                  key={todo.id}
                  title={todo.get('title')}
                  titleStyle={
                    todo.get('done') === true
                      ? Styles.todo_text_done
                      : Styles.todo_text
                  }
                  style={Styles.todo_item}
                  right={props => (
                    <>
                      {/* Todo update button */}
                      {todo.get('done') !== true && (
                        <TouchableOpacity
                          onPress={() => updateTodo(todo.id, true)}>
                          <List.Icon
                            {...props}
                            icon="check"
                            color={'#4CAF50'}
                          />
                        </TouchableOpacity>
                      )}
                      {/* Todo delete button */}
                      <TouchableOpacity onPress={() => deleteTodo(todo.id)}>
                        <List.Icon {...props} icon="close" color={'#ef5350'} />
                      </TouchableOpacity>
                    </>
                  )}
                />
              ))}
          </ScrollView>
        </View>
      </SafeAreaView>
    </>
  );
};

// These define the screen component styles
const Styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFF',
  },
  wrapper: {
    width: '90%',
    alignSelf: 'center',
  },
  header: {
    alignItems: 'center',
    paddingTop: 10,
    paddingBottom: 20,
    backgroundColor: '#208AEC',
  },
  header_logo: {
    width: 170,
    height: 40,
    marginBottom: 10,
    resizeMode: 'contain',
  },
  header_text_bold: {
    color: '#fff',
    fontSize: 14,
    fontWeight: 'bold',
  },
  header_text: {
    marginTop: 3,
    color: '#fff',
    fontSize: 14,
  },
  flex_between: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  create_todo_container: {
    flexDirection: 'row',
  },
  create_todo_input: {
    flex: 1,
    height: 38,
    marginBottom: 16,
    backgroundColor: '#FFF',
    fontSize: 14,
  },
  create_todo_button: {
    marginTop: 6,
    marginLeft: 15,
    height: 40,
  },
  todo_item: {
    borderBottomWidth: 1,
    borderBottomColor: 'rgba(0, 0, 0, 0.12)',
  },
  todo_text: {
    fontSize: 15,
  },
  todo_text_done: {
    color: 'rgba(0, 0, 0, 0.3)',
    fontSize: 15,
    textDecorationLine: 'line-through',
  },
});

TodoList.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
import React, {FC, ReactElement, useState} from 'react';
import {
  Alert,
  View,
  SafeAreaView,
  Image,
  ScrollView,
  StatusBar,
  StyleSheet,
  TouchableOpacity,
} from 'react-native';
import Parse from 'parse/react-native';
import {
  List,
  Title,
  IconButton,
  Text as PaperText,
  Button as PaperButton,
  TextInput as PaperTextInput,
} from 'react-native-paper';

export const TodoList: FC<{}> = ({}): ReactElement => {
  // State variables
  const [readResults, setReadResults] = useState<[Parse.Object]>();
  const [newTodoTitle, setNewTodoTitle] = useState('');

  // Functions used by the screen components
  const createTodo = async function (): Promise<boolean> {
    // This value comes from a state variable
    const newTodoTitleValue: string = newTodoTitle;
    // Creates a new Todo parse object instance
    let Todo: Parse.Object = new Parse.Object('Todo');
    Todo.set('title', newTodoTitleValue);
    Todo.set('done', false);
    // After setting the todo values, save it on the server
    try {
      await Todo.save();
      // Success
      Alert.alert('Success!', 'Todo created!');
      // Refresh todos list to show the new one (you will create this function later)
      readTodos();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const readTodos = async function (): Promise<boolean> {
    // Reading parse objects is done by using Parse.Query
    const parseQuery: Parse.Query = new Parse.Query('Todo');
    try {
      let todos: Parse.Object[] = await parseQuery.find();
      // Be aware that empty or invalid queries return as an empty array
      // Set results to state variable
      setReadResults(todos);
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const updateTodo = async function (
    todoId: string,
    done: boolean,
  ): Promise<boolean> {
    // Create a new todo parse object instance and set todo id
    let Todo: Parse.Object = new Parse.Object('Todo');
    Todo.set('objectId', todoId);
    // Set new done value and save Parse Object changes
    Todo.set('done', done);
    try {
      await Todo.save();
      // Success
      Alert.alert('Success!', 'Todo updated!');
      // Refresh todos list
      readTodos();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  const deleteTodo = async function (todoId: string): Promise<boolean> {
    // Create a new todo parse object instance and set todo id
    let Todo: Parse.Object = new Parse.Object('Todo');
    Todo.set('objectId', todoId);
    // .destroy should be called to delete a parse object
    try {
      await Todo.destroy();
      Alert.alert('Success!', 'Todo deleted!');
      // Refresh todos list to remove this one
      readTodos();
      return true;
    } catch (error) {
      // Error can be caused by lack of Internet connection
      Alert.alert('Error!', error.message);
      return false;
    }
  };

  return (
    <>
      <StatusBar backgroundColor="#208AEC" />
      <SafeAreaView style={Styles.container}>
        <View style={Styles.header}>
          <Image
            style={Styles.header_logo}
            source={ {
              uri:
                'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png',
            } }
          />
          <PaperText style={Styles.header_text_bold}>
            {'React Native on Back4App'}
          </PaperText>
          <PaperText style={Styles.header_text}>{'Product Creation'}</PaperText>
        </View>
        <View style={Styles.wrapper}>
          <View style={Styles.flex_between}>
            <Title>Todo List</Title>
            {/* Todo read (refresh) button */}
            <IconButton
              icon="refresh"
              color={'#208AEC'}
              size={24}
              onPress={() => readTodos()}
            />
          </View>
          <View style={Styles.create_todo_container}>
            {/* Todo create text input */}
            <PaperTextInput
              value={newTodoTitle}
              onChangeText={text => setNewTodoTitle(text)}
              label="New Todo"
              mode="outlined"
              style={Styles.create_todo_input}
            />
            {/* Todo create button */}
            <PaperButton
              onPress={() => createTodo()}
              mode="contained"
              icon="plus"
              color={'#208AEC'}
              style={Styles.create_todo_button}>
              {'Add'}
            </PaperButton>
          </View>
          <ScrollView>
            {/* Todo read results list */}
            {readResults !== null &&
              readResults !== undefined &&
              readResults.map((todo: Parse.Object) => (
                <List.Item
                  key={todo.id}
                  title={todo.get('title')}
                  titleStyle={
                    todo.get('done') === true
                      ? Styles.todo_text_done
                      : Styles.todo_text
                  }
                  style={Styles.todo_item}
                  right={props => (
                    <>
                      {/* Todo update button */}
                      {todo.get('done') !== true && (
                        <TouchableOpacity
                          onPress={() => updateTodo(todo.id, true)}>
                          <List.Icon
                            {...props}
                            icon="check"
                            color={'#4CAF50'}
                          />
                        </TouchableOpacity>
                      )}
                      {/* Todo delete button */}
                      <TouchableOpacity onPress={() => deleteTodo(todo.id)}>
                        <List.Icon {...props} icon="close" color={'#ef5350'} />
                      </TouchableOpacity>
                    </>
                  )}
                />
              ))}
          </ScrollView>
        </View>
      </SafeAreaView>
    </>
  );
};

// These define the screen component styles
const Styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#FFF',
  },
  wrapper: {
    width: '90%',
    alignSelf: 'center',
  },
  header: {
    alignItems: 'center',
    paddingTop: 10,
    paddingBottom: 20,
    backgroundColor: '#208AEC',
  },
  header_logo: {
    width: 170,
    height: 40,
    marginBottom: 10,
    resizeMode: 'contain',
  },
  header_text_bold: {
    color: '#fff',
    fontSize: 14,
    fontWeight: 'bold',
  },
  header_text: {
    marginTop: 3,
    color: '#fff',
    fontSize: 14,
  },
  flex_between: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  create_todo_container: {
    flexDirection: 'row',
  },
  create_todo_input: {
    flex: 1,
    height: 38,
    marginBottom: 16,
    backgroundColor: '#FFF',
    fontSize: 14,
  },
  create_todo_button: {
    marginTop: 6,
    marginLeft: 15,
    height: 40,
  },
  todo_item: {
    borderBottomWidth: 1,
    borderBottomColor: 'rgba(0, 0, 0, 0.12)',
  },
  todo_text: {
    fontSize: 15,
  },
  todo_text_done: {
    color: 'rgba(0, 0, 0, 0.3)',
    fontSize: 15,
    textDecorationLine: 'line-through',
  },
});

If your component is properly set up, you should see something like this after building and running the app:

React Native Back4App

Go ahead and add some to-do’s by typing its titles in the input box one at a time and pressing on the Add button. Note that after every successful creation, the createTodo function triggers the readTodos one, refreshing your task list automatically. You should now have a sizeable to-do list like this:

React Native Back4App

You can now mark your tasks as done by clicking in the checkmark beside it, causing their done value to be updated to true and changing its icon status on the left.

React Native Back4App

The only remaining data operation is now the delete one, which can be done by pressing on the trash can icon at the far right of your to-do list object. After successfully deleting an object, you should get an alert message like this:

React Native Back4App

Conclusion

At the end of this guide, you learned how to perform basic data operations (CRUD) in Parse on React Native. In the next guide, we will show you which data types are supported in Parse and how to use them.