{{announcement.body}}
{{announcement.title}}

How to Use Backendless With React.js, Part 2: CRUD Tutorial

DZone 's Guide to

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!

· Web Dev Zone ·
Free Resource
React.js development

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 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 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 nameand address columns.

Now try to add a new person without an addressvalue:

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.

Topics:
web dev ,react.js tutorial ,crud tutorial ,front-end development

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}