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:
- A React Native App created and connected to Back4App.
- If you want to test/use the screen layout provided by this guide, you should set up the
react-native-paper
library.
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:
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:
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.
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:
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.