Blog

How to Use Backendless with ReactJS (Part 2)

by on March 28, 2019

How to Use Backendless with ReactJS
In the previous article in this series, we started working on a single-page application which is based on a combination of ReactJS and Redux, 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 the 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.

First, let’s create a new file called ./src/persons/index.js and give it the following code:

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. Please share in comments which style you prefer and why as I would be happy to hear other opinions on the subject. 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 your App.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 or updatePerson – 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 the objectId 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.
How to Use Backendless with ReactJS
Let’s also check in the Backendless Dev Console:
How to Use Backendless with ReactJS
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.
How to Use Backendless with ReactJS
Now try to add a new person without an address value:
Test adding a new person without address
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 option. 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>
<DeleteConfirmation
{...deleteConfirmationProps}
show={showDeleteConfirmation}
onHide={this.hideDeleteConfirmation}
/>

Let’s check it:
Test delete person

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 on github.com here. In the next article of the series, we will add 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.      

Leave a Reply