How to Use Backendless With React.js, Part 2: CRUD Tutorial
We pick up where we left off last time, by adding CRUD operations to our application. Read on to get stared!
Join the DZone community and get the full member experience.
Join For Free
In the previous article in this series, we started working on a single-page application which is based on a combination of and with Backendless for the backend. If you missed that article, we recommend you to start there. If you already have a Backendless account and you are already familiar with a React/Redux stack, you can just clone our previous progress from this commit, create a new Backendless app, and use it as an entry point for today's article. Let me describe the main goal for this article and what we plan to cover:
- Create a separate component for our persons list.
- Add a
PersonsEditor
for creating and updating persons. - And add an ability to delete Persons.
Persons Data Container
As you can imagine, our app will grow up quite a bit during this article, so we need to move logic for rendering our list of persons to a separate class/component. There we will keep a data connection and UI representation, and it will also help us in the future to manage Real-Time subscriptions.
Create/Update Person
Typically, creating and updating some entity goes through the same input form. The same is true in our case. We have only two fields, and , and both fields must be provided for a Person, so we will implement the ability to both create and update that Person in this section.
First, let’s create a new file called ./src/persons/index.js
and give it the following code:
JavaScript
import React, { Component } from 'react';
import { connect } from 'react-redux'
import { loadPersons, getPersons } from '../store';
const mapStateToProps = state => {
const { loading, loaded, error, list: persons } = getPersons(state);
return {
loading,
loaded,
error,
persons
}
};
class Persons extends Component {
componentWillMount() {
this.props.loadPersons()
}
render() {
const { loading, error, persons } = this.props;
if (loading) {
return (
<div>
Loading...
</div>
)
}
if (error) {
return (
<div className="alert alert-danger">
Error: {error}
</div>
)
}
return (
<ul className="list-group">
{persons.map((person, index) => (
<li key={index} className="list-group-item">
<div>{person.name}</div>
<div className="text-muted small">{person.address}</div>
</li>
))}
</ul>
)
}
}
export default connect(mapStateToProps, { loadPersons })(Persons);
Now we need to clean up our ./src/app.js
file:
import React from 'react';
import Persons from './persons';
export default function App() {
return (
<div className="container">
<div className="header">
<h3>
Backendless React Addresses Book
</h3>
</div>
<Persons/>
</div>
);
}
You may have noticed that I’ve renamed the ./src/App.js
file to ./src/app.js
. This is because I prefer to use kebab-case instead of PascalCase for naming files, and I assume that’s the correct way to call files in a JavaScript project, but create-react-app generates files with React components in PascalCase, so I’m unsure which is true. Also, if you do not want to rename the App.js
file, just keep in mind when you see app.js
elsewhere in this article, it’s corresponding to yourApp.js
.
Create/Update Person
Typically, creating and updating some entity goes through the same input form. The same is true in our case. We have only two fields, name
and address
, and both fields must be provided for a Person, so we will implement the ability to both create and update that Person in this section.
We are going to create a modal dialog for creating/updating Persons. As you recall, we enabled Bootstrap Styles, so let's be consistent and install the react-bootstrap module from NPM. This will give us a bunch of Bootstrap React Components including Modal Dialog.
npm i react-bootstrap -S
Create a new react file named ./src/persons/editor.js
with the following content:
import React, { Component } from 'react';
import { connect } from 'react-redux'
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import Alert from 'react-bootstrap/Alert';
import { createPerson, updatePerson } from '../store';
class PersonEditor extends Component {
constructor(props) {
super(props);
this.state = {
person : props.person || {},
saving : false,
serverError: null,
}
}
componentWillReceiveProps(nextProps) {
const prevPerson = this.props.person || {};
const nextPerson = nextProps.person || {};
if (prevPerson.objectId !== nextPerson.objectId) {
this.setState({ person: nextPerson })
}
}
close = () => {
this.setState({
person : {},
saving : false,
serverError: null
});
this.props.onHide()
};
preparePerson() {
const { person } = this.state;
return {
...person,
name : (person.name || '').trim() || null,
address: (person.address || '').trim() || null,
}
}
save = () => {
const person = this.preparePerson();
const action = this.props.person
? this.props.updatePerson
: this.props.createPerson;
action(person)
.then(() => this.close())
.catch(e => this.setState({ serverError: e.message }));
};
onNameChange = e => this.setState({ person: { ...this.state.person, name: e.target.value } });
onAddressChange = e => this.setState({ person: { ...this.state.person, address: e.target.value } });
render() {
const { show } = this.props;
const { person, serverError, saving } = this.state;
const isNew = !this.props.person;
return (
<Modal show={show} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title>
{isNew ? 'Add' : 'Edit'} Person
</Modal.Title>
</Modal.Header>
<Modal.Body>
<form>
<div className="form-group">
<label>Name:</label>
<input
className="form-control"
placeholder="Input name"
value={person.name || ''}
onChange={this.onNameChange}
/>
</div>
<div className="form-group">
<label>Address:</label>
<input
className="form-control"
placeholder="Input address"
value={person.address || ''}
onChange={this.onAddressChange}
/>
</div>
{serverError && (
<Alert variant="danger">
{serverError}
</Alert>
)}
</form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.close}>
Close
</Button>
<Button variant="primary" onClick={this.save} disabled={saving}>
{saving ? 'Saving...' : 'Save'}
</Button>
</Modal.Footer>
</Modal>
);
}
}
export default connect(null, { createPerson, updatePerson })(PersonEditor);
Let's see what the component does, exactly:
- Renders Modal Dialog for creating/updating a Person.
- Has two inputs for the Person's name and address, and on change, keeps the changes in the component's state.
- On click at "Save" button, it will collect the Person object and pass it to either
createPerson
orupdatePerson
— which action will be used depends on whether the user opens the dialog for creating or for updating. - On save, catches an error from the server and displays it.
Assuming no problems thus far, let’s add actions and reducers in order to create or update Persons. For that, add a few methods in the ./src/store/actions/persons.js
file:
export const createPerson = person => ({
types : [null, t.CREATE_PERSON_SUCCESS, null],
apiCall: () => Backendless.Data.of('Person').save(person),
});
export const updatePerson = person => ({
types : [null, t.UPDATE_PERSON_SUCCESS, null],
apiCall: () => Backendless.Data.of('Person').save(person),
});
Basically, for the purpose of creating and updating, the Backendless JS-SKD has a single method, Backendless.Data.of('TABLE_NAME').save(object)
, but under the hood it decides which method to use, POST or PUT. If the passed object has theobjectId
property, PUT will be used. Otherwise, POST is used.
As you can see, we do not specify START_ACTION
and ACTION_FAIL
because we aren’t interested in when the request starts and when it’s failed because we catch it in our PersonEditor
Component.
Also, we need to declare two action types in ./src/store/action-types.js
:
CREATE_PERSON_SUCCESS: null,
UPDATE_PERSON_SUCCESS: null,
And add reducers for modifying store when a new Person is created or updated. Change ./src/store/reducers/persons.js
a little:
import t from '../action-types'
import { reduceReducers, loadReducer, reducersMap } from './helpers'
const initialState = {
list: []
};
const personsReducer = reduceReducers(initialState,
loadReducer(t.LOAD_PERSONS, (state, action) => ({
...state,
list: action.result
})),
reducersMap({
[t.CREATE_PERSON_SUCCESS]: (state, action) => ({
...state,
list: state.list.concat(action.result)
}),
[t.UPDATE_PERSON_SUCCESS]: (state, action) => ({
...state,
list: state.list.map(oldPerson => oldPerson.objectId === action.result.objectId ? action.result : oldPerson)
})
})
);
export default personsReducer
The last step that we have to do is modify our ./src/person/index.js
file:
- Import the
PersonEditor
component. - Add a new button to "Add new Person."
- For each person item in the list, add an "Edit" button.
When we're finished, the code needs to look like this:
import React, { Component } from 'react';
import { connect } from 'react-redux'
import Button from 'react-bootstrap/Button';
import { loadPersons, getPersons } from '../store';
import Editor from './editor';
const mapStateToProps = state => {
const { loading, loaded, error, list: persons } = getPersons(state);
return {
loading,
loaded,
error,
persons
}
};
class Persons extends Component {
state = {
showEditor : false,
editorProps: null
};
showEditor = person => this.setState({ showEditor: true, editorProps: { person } });
hideEditor = () => this.setState({ showEditor: false, editorProps: null });
componentWillMount() {
this.props.loadPersons();
}
onAddClick = () => this.showEditor(null);
onEditClick = person => this.showEditor(person);
renderPerson = person => {
return (
<li key={person.objectId} className="list-group-item d-flex justify-content-between align-items-center">
<div>
<div>{person.name}</div>
<div className="text-muted small">{person.address}</div>
</div>
<Button size="sm" variant="outline-primary" onClick={() => this.onEditClick(person)}>Edit</Button>
</li>
);
};
render() {
const { loading, error, persons } = this.props;
const { showEditor, editorProps } = this.state;
if (loading) {
return (
<div>
Loading...
</div>
)
}
if (error) {
return (
<div className="alert alert-danger">
Error: {error}
</div>
)
}
return (
<div>
<div className="mb-2">
<Button onClick={this.onAddClick}>Add new Person</Button>
<Editor {...editorProps} show={showEditor} onHide={this.hideEditor}/>
</div>
<ul className="list-group">
{persons.map(this.renderPerson)}
</ul>
</div>
)
}
}
export default connect(mapStateToProps, { loadPersons })(Persons);
Alright, let's check what we have.

Let's also check in the Backendless Dev Console:

As you recall, we catch an error from the server upon saving or updating a Person. This is because there is not any validation and every request, even if it does not include Person name
or address
, will be passed. So, let’s add a Not Null (Required)
constraint for both the name
and address
columns.

Now try to add a new person without an address
value:

As you can see, the server does not allow us to create a new Person without address
, and we're successfully processing this requirement.
Delete Person
Deleting, in most cases, is a dangerous operation and must have some preventative options. Thus, we have to add a DeletePersonConfirmation
dialog to prevent deleting with a wrong click.
Create a new ./src/persons/delete-confirmation.js
file with the following content:
import React, { Component } from 'react';
import { connect } from 'react-redux'
import Modal from 'react-bootstrap/Modal';
import Button from 'react-bootstrap/Button';
import { removePerson } from '../store';
class DeletePersonConfirmation extends Component {
state = {
removing: false,
};
close = () => {
this.setState({
removing: false,
});
this.props.onHide()
};
confirm = () => {
const { person } = this.props;
this.setState({ removing: true });
this.props.removePerson(person.objectId)
.then(() => this.close())
};
render() {
const { person, show } = this.props;
const { removing } = this.state;
return (
<Modal show={show} onHide={this.close}>
<Modal.Header className="bg-danger text-white" closeButton>
<Modal.Title>
Delete Person Confirmation
</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>
Are you seriously wanting to delete "<b>{person && person.name}</b>"
</p>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.close} disabled={removing}>No</Button>
<Button variant="danger" onClick={this.confirm} disabled={removing}>Yes</Button>
</Modal.Footer>
</Modal>
);
}
}
export default connect(null, { removePerson })(DeletePersonConfirmation);
Add an action in ./src/store/actions/persons.js
:
export const removePerson = personId => ({
personId,
types : [null, t.REMOVE_PERSON_SUCCESS, null],
apiCall: () => Backendless.Data.of('Person').remove(personId),
});
Don’t forget about action type t.REMOVE_PERSON_SUCCESS
for modifying store. Add a reducer for the action as well:
const personsReducer = reduceReducers(initialState,
...
reducersMap({
...
[t.REMOVE_PERSON_SUCCESS]: (state, action) => ({
...state,
list: state.list.filter(person => person.objectId !== action.personId)
})
})
);
Similarly, with PersonEditor
, modify the ./src/persons/index.js
file:
state = {
...
showDeleteConfirmation : false,
deleteConfirmationProps: null,
};
showDeleteConfirmation = person => this.setState({ showDeleteConfirmation : true, deleteConfirmationProps: { person } });
hideDeleteConfirmation = () => this.setState({ showDeleteConfirmation: false, deleteConfirmationProps: null });
onDeleteClick = person => this.showDeleteConfirmation(person);
<Button size="sm" variant="outline-danger" onClick={() => this.onDeleteClick(person)}>Delete</Button>
JavaScript
<DeleteConfirmation
{...deleteConfirmationProps}
show={showDeleteConfirmation}
onHide={this.hideDeleteConfirmation}
/>
Let's check it:

Conclusion
That's all for today. We've implemented adding, updating, and deleting persons, and all the changes that we made today you can see here, on github.com. In the next article of the series, we will add a Real-Time database and I will show you how to add subscriptions to monitor changes for Person table.
Thanks for reading and see you soon.
If you enjoyed this article and want to learn more about React, check out this collection of tutorials and articles on all things React.
Published at DZone with permission of Vladimir Upirov, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Transactional Outbox Patterns Step by Step With Spring and Kotlin
-
Comparing Cloud Hosting vs. Self Hosting
-
Operator Overloading in Java
-
What Is Envoy Proxy?
Comments