React CRUD tutorial
Introduction
Storing data on Parse is built around the 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. 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 App created and connected to Back4App.
- If you want to test/use the screen layout provided by this guide, you should set up the
Ant Design
library.
Goal
To build a basic CRUD application in React 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 to-do will have a title (string
) describing the task and a done(boolean
) field indicating whether 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 to-do values, save it on the server
try {
await Todo.save();
// Success
alert('Success! To-do created!');
// Refresh to-dos 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(`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 to-do values, save it on the server
try {
await Todo.save();
// Success
alert('Success! To-do created!');
// Refresh to-dos list to show the new one (you will create this function later)
readTodos();
return true;
} catch (error: any) {
// Error can be caused by lack of Internet connection
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(`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: any) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error.message}`);
return false;
};
};
Many constraints and orderings can be applied to your queries using the Parse.Query
class, but 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('Success! To-do updated!');
// Refresh to-dos list
readTodos();
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
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('Success! To-do updated!');
// Refresh to-dos list
readTodos();
return true;
} catch (error: any) {
// Error can be caused by lack of Internet connection
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('Success! To-do deleted!');
// Refresh to-dos list to remove this one
readTodos();
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
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('Success! To-do deleted!');
// Refresh to-dos list to remove this one
readTodos();
return true;
} catch (error: any) {
// Error can be caused by lack of Internet connection
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 component
Here is the complete component code, including styled user interface elements (using Ant Design
), state variables, and calls to your CRUD functions. You should create a separate component in a file called TodoList.js/TodoList.tsx
in your src
directory containing the following code or add it directly to your main application file (App.js/App.tsx
).
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
import React, { useState } from 'react';
import Parse from 'parse/dist/parse.min.js';
import './App.css';
import { Button, Input, List } from 'antd';
import {
CheckOutlined,
CloseOutlined,
PlusOutlined,
RedoOutlined,
} from '@ant-design/icons';
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 to-do values, save it on the server
try {
await Todo.save();
// Success
alert('Success! To-do created!');
// Refresh to-dos 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(`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(`Error! ${error.message}`);
return false;
}
};
const updateTodo = async function (todoId, done) {
// Create a new to-do 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('Success! To-do updated!');
// Refresh todos list
readTodos();
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
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('Success! To-do deleted!');
// Refresh to-dos list to remove this one
readTodos();
return true;
} catch (error) {
// Error can be caused by lack of Internet connection
alert(`Error! ${error.message}`);
return false;
}
};
return (
<div>
<div className="header">
<img
className="header_logo"
alt="Back4App Logo"
src={
'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png'
}
/>
<p className="header_text_bold">{'React on Back4App'}</p>
<p className="header_text">{'To-do List'}</p>
</div>
<div className="container">
<div className="flex_between">
<h2 className="list_heading">Todo List</h2>
{/* To-do read (refresh) button */}
<Button
type="primary"
shape="circle"
color={'#208AEC'}
size={'default'}
onClick={readTodos}
icon={<RedoOutlined />}
></Button>
</div>
<div className="new_todo_wrapper flex_between">
{/* Todo create text input */}
<Input
value={newTodoTitle}
onChange={(event) => setNewTodoTitle(event.target.value)}
placeholder="New Todo"
size="large"
/>
{/* Todo create button */}
<Button
type="primary"
className="create_todo_button"
color={'#208AEC'}
size={'large'}
onClick={createTodo}
icon={<PlusOutlined />}
>
Add
</Button>
</div>
<div>
{/* Todo read results list */}
{readResults !== null &&
readResults !== undefined &&
readResults.length > 0 && (
<List
dataSource={readResults}
renderItem={(item) => (
<List.Item className="todo_item">
<p
className={
item.get('done') === true
? 'todo_text_done'
: 'todo_text'
}
>
{item.get('title')}
</p>
<div className="flex_row">
{/* Todo update button */}
{item.get('done') !== true && (
<Button
type="primary"
shape="circle"
className="todo_button"
onClick={() => updateTodo(item.id, true)}
icon={
<CheckOutlined className="todo_button_icon_done" />
}
></Button>
)}
{/* Todo delete button */}
<Button
type="primary"
shape="circle"
className="todo_button"
onClick={() => deleteTodo(item.id)}
icon={
<CloseOutlined className="todo_button_icon_remove" />
}
></Button>
</div>
</List.Item>
)}
/>
)}
</div>
</div>
</div>
);
};
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
import React, { useState, FC, ReactElement } from 'react';
import './App.css';
import { Button, Input, List } from 'antd';
import {
CheckOutlined,
CloseOutlined,
PlusOutlined,
RedoOutlined,
} from '@ant-design/icons';
const Parse = require('parse/dist/parse.min.js');
export const TodoList: FC<{}> = (): ReactElement => {
// State variables
const initialReadResults: Parse.Object[] = [];
const [readResults, setReadResults] = useState(initialReadResults);
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 to-do values, save it on the server
try {
await Todo.save();
// Success
alert('Success! To-do created!');
// Refresh to-dos list to show the new one (you will create this function later)
readTodos();
return true;
} catch (error: any) {
// Error can be caused by lack of Internet connection
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: any) {
// Error can be caused by lack of Internet connection
alert('Error!' + error.message);
return false;
}
};
const updateTodo = async function (todoId: string, done: boolean): Promise<boolean> {
// Create a new to-do 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('Success! To-do updated!');
// Refresh todos list
readTodos();
return true;
} catch (error: any) {
// Error can be caused by lack of Internet connection
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('Success! To-do deleted!');
// Refresh to-dos list to remove this one
readTodos();
return true;
} catch (error: any) {
// Error can be caused by lack of Internet connection
alert('Error!' + error.message);
return false;
}
};
return (
<div>
<div className="header">
<img
className="header_logo"
alt="Back4App Logo"
src={
'https://blog.back4app.com/wp-content/uploads/2019/05/back4app-white-logo-500px.png'
}
/>
<p className="header_text_bold">{'React on Back4App'}</p>
<p className="header_text">{'To-do List'}</p>
</div>
<div className="container">
<div className="flex_between">
<h2 className="list_heading">Todo List</h2>
{/* To-do read (refresh) button */}
<Button
type="primary"
shape="circle"
color={'#208AEC'}
onClick={readTodos}
icon={<RedoOutlined />}
></Button>
</div>
<div className="new_todo_wrapper flex_between">
{/* Todo create text input */}
<Input
value={newTodoTitle}
onChange={(event: {target: {value: string}}) => setNewTodoTitle(event.target.value)}
placeholder="New Todo"
size="large"
/>
{/* Todo create button */}
<Button
type="primary"
className="create_todo_button"
color={'#208AEC'}
size={'large'}
onClick={createTodo}
icon={<PlusOutlined />}
>
Add
</Button>
</div>
<div>
{/* Todo read results list */}
{readResults !== null &&
readResults !== undefined &&
readResults.length > 0 && (
<List
dataSource={readResults}
renderItem={(item: Parse.Object) => (
<List.Item className="todo_item">
<p
className={
item.get('done') === true
? 'todo_text_done'
: 'todo_text'
}
>
{item.get('title')}
</p>
<div className="flex_row">
{/* Todo update button */}
{item.get('done') !== true && (
<Button
type="primary"
shape="circle"
className="todo_button"
onClick={() => updateTodo(item.id, true)}
icon={
<CheckOutlined className="todo_button_icon_done" />
}
></Button>
)}
{/* Todo delete button */}
<Button
type="primary"
shape="circle"
className="todo_button"
onClick={() => deleteTodo(item.id)}
icon={
<CloseOutlined className="todo_button_icon_remove" />
}
></Button>
</div>
</List.Item>
)}
/>
)}
</div>
</div>
</div>
);
};
Also, add these CSS styles at the end of your App.css
file:
App.css
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
/* ... */
/* Your other styles */
/* Back4App Guide Styles */
html {
box-sizing: border-box;
outline: none;
overflow: auto;
}
*,
*:before,
*:after {
margin: 0;
padding: 0;
box-sizing: inherit;
}
h1,
h2,
h3,
h4,
h5,
h6 {
margin: 0;
}
p {
margin: 0;
}
body {
margin: 0;
background-color: #fff;
}
.container {
width: 100%;
max-width: 600px;
margin: auto;
padding: 20px 0;
}
.wrapper {
width: '90%';
align-self: 'center';
}
.header {
align-items: center;
padding: 25px 0;
background-color: #208AEC;
}
.header_logo {
height: 55px;
margin-bottom: 20px;
object-fit: contain;
}
.header_text_bold {
margin-bottom: 3px;
color: rgba(255, 255, 255, 0.9);
font-size: 16px;
font-weight: bold;
}
.header_text {
color: rgba(255, 255, 255, 0.9);
font-size: 15px;
}
.flex_row {
display: flex;
}
.flex_between {
display: flex;
align-items: center;
justify-content: space-between;
}
.list_heading {
font-weight: bold;
}
.new_todo_wrapper {
margin-top: 20px;
margin-bottom: 10px;
}
.new_todo_wrapper > input {
margin-right: 20px;
}
.todo_item {
border-bottom-width: 1;
border-bottom-color: 'rgba(0, 0, 0, 0.12)';
}
.todo_text {
font-size: 15px;
}
.todo_text_done {
color: rgba(0, 0, 0, 0.3);
font-size: 15px;
text-decoration-line: line-through;
}
.todo_button {
width: 32px;
height: 32px;
margin-left: 5px;
background-color: transparent;
border-radius: 50px;
border: none;
cursor: pointer;
}
.todo_button:hover,
.todo_button:focus {
background-color: rgba(0, 0, 0, 0.1);
}
.todo_button_icon_done {
color: #52c41a;
font-size: 16px;
}
.todo_button_icon_remove {
color: #f5222d;
font-size: 16px;
}
If your component is properly set up, you should see something like this after running the app:
Go ahead and add some to-dos 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. In the next guide, we will show you which data types are supported in Parse and how to use them.