14Jul
Create simple POS with React.js, Node.js, and MongoDB #8: CRUD POS Machine
Create simple POS with React.js, Node.js, and MongoDB #8: CRUD POS Machine

Defenition: POS – “Point of Sale”. At the point of sale, the merchant calculates the amount owed by the customer, indicates that amount, may prepare an invoice for the customer (which may be a cash register printout), and indicates the options for the customer to make payment.

In the previous tutorial, we completed the implementation of the redux mechanism in our register and Forgot Password components. This is the continuation of the same tutorial from where we left off.

In this tutorial, we move forward to CRUD operations for POS machine data. CRUD stands for Create, Read, Update, and Delete operations. Here, we are going to implement the CRUD operations for POS machine data.

So, let’s get started!!

Preparing States

Frontend

In this section, we are going to add a new redux function that we want to use.

First, we start with the preparation of the redux component with the new redux state in constant.js. Then, we add a new state for Delete operation. First, we export the states as shown in the code snippet below;

// POS Machine
export const POSMACHINE_FETCHING = "POSMACHINE_FETCHING";
export const POSMACHINE_SUCCESS = "POSMACHINE_SUCCESS";
export const POSMACHINE_FAILED = "POSMACHINE_FAILED";
export const POSMACHINE_CLEAR = "POSMACHINE_CLEAR";

Next, we are going to create new reducer name posmachine.reducer.js and handle the state dispatches with the following piece of code:

import {
  POSMACHINE_FETCHING,
  POSMACHINE_SUCCESS,
  POSMACHINE_FAILED,
  POSMACHINE_CLEAR,
} from "../constants";

const initialState = {
  isFetching: false,
  isError: false,
  result: null,
};

export default (state = initialState, { type, payload }) => {
  switch (type) {
    case POSMACHINE_FETCHING:
      return { ...state, isFetching: true, isError: false, result: null };
    case POSMACHINE_FAILED:
      return { ...state, isFetching: false, isError: true, result: null };
    case POSMACHINE_SUCCESS:
      return { ...state, isFetching: false, isError: false, result: payload };
    case POSMACHINE_CLEAR:
      return { ...state, result: null, isFetching: false, isError: false };
    default:
      return state;
  }
};

Then, we import the new register configuration and combine the reducers with other reducers that we previously implemented. The combined reducer configuration is exported as shown in the code snippet below:

import posmachineReducer from "./posmachine.reducer";
export default combineReducers({
  loginReducer,
  registerReducer,
  forgotpasswordReducer,
  resetpasswordReducer,
  posmachineReducer,
});

Next, we are going to create a new action file name posmachine.action.js. Then, we need to import states and functions just as in previous chapters. It is similar to previous action files that we have created. But, the only difference is the presence of delete operation. The overall code for this action configuration is provided in the code snippet below:

import {
  POSMACHINE_FETCHING,
  POSMACHINE_SUCCESS,
  POSMACHINE_FAILED,
  POSMACHINE_CLEAR,
  server,
} from "../constants";
import swal from "sweetalert";
import { httpClient } from "./../utils/HttpClient";

export const setPOSMachineStateToFetching = () => ({
  type: POSMACHINE_FETCHING,
});

export const setPOSMachineStateToFailed = () => ({
  type: POSMACHINE_FAILED,
});
export const setPOSMachineStateToClear = () => ({
  type: POSMACHINE_CLEAR,
});
export const setPOSMachineStateToSuccess = (payload) => ({
  type: POSMACHINE_SUCCESS,
  payload,
});

Hence, we have completed the Front-end part of the work. Now, we move to the backend.

Backend

We need to prepare the backend section before we create a new endpoint. For backend, we are going to create a new schema name pos_machine_schema.js and a new table structure using the mongoose package. The schema and model for the table structure is provided in the code snippet below:

const mongoose = require("mongoose");
const schema = mongoose.Schema({
  alias: String,
  serial_number: String,
  created: { type: Date, default: Date.now },
});
module.exports = mongoose.model("pos_machines", schema);

Next, we need to create a new file named api_pos_machine.js. Then, we need to import the following packages and libraries:

const express = require("express");
const router = express.Router();
const POS_Machine = require("./models/pos_machine_schema");
// new api endpoint start here
module.exports = router;

Then, we need to import the new API file to the main API and use it as a middleware as shown in the code snippet below:

const express = require("express");
const router = express.Router();
require("./db");
router.use(require("./api_auth"));
router.use(require("./api_pos_machine"));
module.exports = router;

Hence, we are done with the backend part as well

Create Operation

Here, we are going to implement the create operation. This operation adds new data. First, we are going to start with the frontend then the backend.

Frontend

First, we create a new action named create as shown in the code snippet below:

export const create = (values, history) => {
  return async (dispatch) => {
    dispatch(setPOSMachineStateToFetching());
    const response = await httpClient.post(
      process.env.REACT_APP_API_URL + "pos_machine",
      values
    );
    if (response.data.result == "success") {
      dispatch(setPOSMachineStateToSuccess(response.data));
      swal("Success!", response.data.message, "success").then((value) => {
        dispatch(setPOSMachineStateToClear());
        history.goBack();
        dispatch(index());
      });
    } else if (response.data.result === "error") {
      dispatch(setPOSMachineStateToFailed());
      swal("Error!", response.data.message, "error");
    }
  };
};

Then, we need to create a new folder name posmachine_create as that screenshot below:

 New Folder Name: "posmachine_create"
New Folder Name: “posmachine_create”

Next, we need to import component and function that we need to use as shown in the code snippet below:

import React, { useState, useEffect } from "react";
import { Formik } from "formik";
import { useSelector, useDispatch } from "react-redux";
import * as posmachineActions from "../../actions/posmachine.action";
import { server } from "../../constants";

Then, we need to create a new redux instance called Pos_machine as shown in the code snippet below:

const Pos_Machine = (props) => {
  const dispatch = useDispatch();
  const posmachineReducer = useSelector(
    ({ posmachineReducer }) => posmachineReducer
  );

Here, we created a dispatch and reducer.

Next, we check the user’s login state with the following piece of code:

useEffect(() => {
    if (localStorage.getItem(server.TOKEN_KEY) === null) {
      return props.history.push("/login");
    }
  }, []);

Now, we need to create a new simple form for getting data as shown in the code snippet below:

const showForm = ({
    values,
    errors,
    touched,
    handleChange,
    handleSubmit,
    isSubmitting,
  }) => {
    return (
      <form onSubmit={handleSubmit}>
        <div className="form-group input-group has-feedback">
          <input
            type="text"
            name="alias"
            onChange={handleChange}
            value={values.alias}
            className="form-control"
            placeholder="POS Machine Alias Name"
            className={
              errors.alias && touched.alias
                ? "form-control is-invalid"
                : "form-control"
            }
          />
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-user"></span>
            </div>
          </div>
          {errors.alias && touched.alias ? (
            <small id="passwordHelp" class="text-danger">
              {errors.alias}
            </small>
          ) : null}
        </div>
        <div className="form-group input-group has-feedback">
          <input
            type="text"
            name="serial_number"
            onChange={handleChange}
            value={values.serial_number}
            className="form-control"
            placeholder="Serial Number"
            className={
              errors.serial_number && touched.serial_number
                ? "form-control is-invalid"
                : "form-control"
            }
          />
          <div class="input-group-append">
            <div class="input-group-text">
              <span class="fas fa-user"></span>
            </div>
          </div>
          {errors.serial_number && touched.serial_number ? (
            <small id="passwordHelp" class="text-danger">
              {errors.serial_number}
            </small>
          ) : null}
        </div>
        <div class="row">
          <div class="offset-md-8 col-4">
            <button
              type="submit"
              disabled={isSubmitting}
              class="btn btn-primary btn-block"
            >
              Add
            </button>
          </div>
        </div>
      </form>
    );
  };

Then, we need to create a main UI function which we can definitely copy from prev chapter. The code is provided in the code snippet below:

return (
    <div class="login-page">
      <div className="register-box">
        <div className="card">
          <div className="card-body register-card-body">
            <p className="login-box-msg">Add Pos Machine Data</p>

            <Formik
              initialValues={{
                alias: "",
                serial_number: "",
              }}
              onSubmit={(values, { setSubmitting }) => {
           
                dispatch(posmachineActions.create(values, props.history));
                setSubmitting(false);
              }}
              // validationSchema={Create_Schema}
            >
              {/* {this.showForm()}            */}
              {(props) => showForm(props)}
            </Formik>
          </div>
          {/* /.form-box */}
        </div>
        {/* /.card */}
      </div>
    </div>
  );
};

Next, we need to register the component to App.js. First, we need to import using the following piece of code:

import PosMachineCreate from "./components/posmachine_create";

Then, we need to register a new route as shown below:

         <SecuredRoute
            path="/posmachine/create"
            component={PosMachineCreate}
          />

Here, when we navigate to http://localhost:3000/posmachine/create, we get the following result:

Create Form
Create Form

Backend

In the backend part, we need to create a new API endpoint in order to add a new row. For that, we can use the code from the following code snippet:

router.post("/pos_machine", async (req, res) => {
  try {
    let doc = await POS_Machine.create(req.body);

    res.json({
      result: "success",
      message: "Create new POS data Successfully",
    });
  } catch (err) {
    res.json({ result: "error", message: err.errmsg });
  }
});

Now, we can try to submit new data. Since we do not have any view in order to see the inserted data, we can open the database GUI to see the new data as shown in the code snippet below:

Database Row
Database Row

Index Operation

Now, we move on to the index i.e. read operation. First, we are going to implement the backend API endpoint then migrate to the implementation of the frontend part.

Backend

Here, we create a new API endpoint as shown in the code snippet below:

router.get("/pos_machine", async (req, res) => {
  try {
    let data = await POS_Machine.find({}).sort({ created: -1 });
    res.json({
      result: "success",
      message: "Fetch POS data Successfully",
      data: data,
    });
  } catch (err) {
    res.json({ result: "error", message: err.msg });
  }
});

Frontend

Next, we create a new index in order to display data. As in the previous section, we create a new action for fetching data from API in pos_machine.action.js. The code for the action configuration is provided in the code snippet below:

export const index = () => {
  return async (dispatch) => {
    dispatch(setPOSMachineStateToFetching);
    const response = await httpClient.get(
      process.env.REACT_APP_API_URL + "pos_machine"
    );
    if (response.data.result == "success") {
      // console.log(response.data);
      dispatch(setPOSMachineStateToSuccess(response.data.data));
    } else if (response.data.result === "error") {
      dispatch(setPOSMachineStateToFailed());
      swal("Error!", response.data.message, "error");
    }
  };
};

Next, we are going to create a new component name posmachine_index as shown in the screenshot below:

New Component Name "posmachine_index" 
New Component Name “posmachine_index”

Then, we import the necessary components:

import React, { useState, useEffect } from "react";
import * as posmachineActions from "../../actions/posmachine.action";
import { server } from "../../constants";
import { useSelector, useDispatch } from "react-redux";
import { Link } from "react-router-dom";
import swal from "sweetalert";

And create a new functional component called Pos_Machine_Index . Then, we create a new instance from the hook as shown in the code snippet below:

const Pos_Machine_Index = (props) => {
  const posmachineReducer = useSelector(
    ({ posmachineReducer }) => posmachineReducer
  );
  const dispatch = useDispatch();

Here, we add middleware to detect login token and launch action in order to get data from API. The code for this operations is provided in the code snippet below:

useEffect(() => {
    if (localStorage.getItem(server.TOKEN_KEY) === null) {
      return props.history.push("/login");
    }
    dispatch(posmachineActions.index());
  }, []);

In the UI part, we create a new table and use simple for loop to display data in rows. The code to do this is provided in the code snippet below:

<div className="card-body table-responsive p-0">
                  <table className="table table-hover text-nowrap">
                    <thead>
                      <tr>
                        <th>Alias</th>
                        <th>Serial Name</th>
                        <th>Created Date</th>
                        <th>Action</th>
                      </tr>
                    </thead>
                    <tbody>
                      {posmachineReducer.result ? (
                        posmachineReducer.result.map((data, index) => {
                          return (
                            <tr key={index}>
                              <td>{data.alias}</td>
                              <td>{data.serial_number}</td>
                              <td>{data.created}</td>
                              <td>
                                <Link to={"/posmachine/update/" + data._id}>
                                  Edit
                                </Link>
                                {" | "}
                                <Link onClick={() => confirmDelete(data._id)}>
                                  Delete
                                </Link>
                              </td>
                            </tr>
                          );
                        })
                      ) : (
                        <td></td>
                      )}
                    </tbody>
                  </table>
                </div>

Now, if we navigating to http://localhost:3000/posmachine, we also see data and link to other activities as shown in the screenshot below:

Index Screen
Index Screen

Thus, the index operation ends here, Now, we move on to implement edit operation that is available in the row of data as shown in the screenshot above.

Edit Operation

In the edit section, we will fetch data and populate it to form. Then we can perform the update operation.

Populate form

In order to fetch data, we will get the id when we click on index row then pass it to action and perform a request for query data from API.

In posmachine.action.js, we need to create a new function to receive id then fetch from API. If successful, we store data to the state.The coding implementation for this is provided in the code snippet below:

export const getPosMachineById = (id) => {
  return async (dispatch) => {
    dispatch(setPOSMachineStateToFetching());
    const response = await httpClient.get(
      process.env.REACT_APP_API_URL + "pos_machine/" + id
    );
    if (response.data.result == "success") {
      dispatch(setPOSMachineStateToSuccess(response.data.data));
    } else if (response.data.result === "error") {
      dispatch(setPOSMachineStateToFailed());
      swal("Error!", response.data.message, "error");
    }
  };
};

Next, we need to create new component name posmachine_update as shown in the screenshot below:

 New Component Name "posmachine_update"
New Component Name “posmachine_update”

Here, we can copy everything from the posmachine_create component.

in order to fetch data, we call the action when componentWillMount fires as shown in the code snippet below:

useEffect(() => {
    if (localStorage.getItem(server.TOKEN_KEY) === null) {
      return props.history.push("/login");
    }
    const { id } = props.match.params;

    dispatch(posmachineActions.getPosMachineById(id));
  }, []);

Now, we can observe that we capture params and extract id then pass to action that we created.

Next important part is to add data that we send to server from Formik as shown in the code snippet below:

            <Formik
              enableReinitialize={true}
              initialValues={
                posmachineReducer.result
                  ? posmachineReducer.result
                  : { alias: "", serial_number: "" }
              }

enableReinitialize allows us to repopulate form again. This is because the form will load success until we send a request for getting data.

initialValues, we store data from API to state and initialize posmachineReducer.result as null. Hence, we use conditions to prevent errors.

Backend

Here, we need to create a new endpoint that captures id and use query to get data and send back to the client. The implementation is provided in the code snippet below:

router.get("/pos_machine/:id", async (req, res) => {
  try {
    let data = await POS_Machine.findById({ _id: req.params.id });
    res.json({
      result: "success",
      message: "Fetch Single POS data Successfully",
      data: data,
    });
  } catch (err) {
    res.json({ result: "error", message: err.msg });
  }
});

Now, we can click the link and see that the data will populate the form as shown in the simulation below:

Populate Form Result
Populate Form Result

We are not done yet.

Now, we need to create a new endpoint in order to update data by receiving the id then query and update data. We can do this by using the code in the code snippet given below:

router.put("/pos_machine", async (req, res) => {
  try {
    let doc = await POS_Machine.findByIdAndUpdate(
      { _id: req.body._id },
      req.body
    );

    res.json({
      result: "success",
      message: "Update POS data Successfully",
    });
  } catch (err) {
    res.json({ result: "error", message: err.msg });
  }
});

Back to the frontend, we create a new action named update in posmachine.action.js. Here we can just copy code from create function but change from post to put method as shown in the code snippet below:

export const update = (values, history) => {
  return async (dispatch) => {
    dispatch(setPOSMachineStateToFetching());
    const response = await httpClient.put(
      process.env.REACT_APP_API_URL + "pos_machine",
      values
    );
    if (response.data.result == "success") {
      dispatch(setPOSMachineStateToClear());
      history.goBack();
      dispatch(index());
    } else if (response.data.result === "error") {
      dispatch(setPOSMachineStateToFailed());
      swal("Error!", response.data.message, "error");
    }
  };
};

Then, when the update is a success, we clear all data in the state by calling setPOSMachineStateToClear action then redirect to index.

Then, we use call index action for fetching new data again.

After that, we call update action in UI as shown in the code snippet below:

             <Formik
              enableReinitialize={true}
              initialValues={
                posmachineReducer.result
                  ? posmachineReducer.result
                  : { alias: "", serial_number: "" }
              }
              onSubmit={(values, { setSubmitting }) => {
                console.log(values);
                dispatch(posmachineActions.update(values, props.history));
                setSubmitting(false);
              }}

Now, we can perform the update operation as shown in the simulation screenshot below:

Performing the Update Operation
Performing the Update Operation

This completes our Edit/Update operation. Now, its time for delete operation.

Delete Operation

Delete operation is simpler than the rest. We just click the link in the index then popup the confirm dialog in order to delete the data. Then, we fetch data new again to display the remaining data.

Frontend

In posmachine.action.js, we need to add a new function called remove as shown in the code snippet below:

export const remove = (id) => {
  return async (dispatch) => {
    console.log("remove");
    dispatch(setPOSMachineStateToFetching());
    const response = await httpClient.delete(
      process.env.REACT_APP_API_URL + "pos_machine/" + id
    );
    if (response.data.result == "success") {
      dispatch(setPOSMachineStateToSuccess());
      dispatch(index());
    } else if (response.data.result === "error") {
      dispatch(setPOSMachineStateToFailed());
      swal("Error!", response.data.message, "error");
    }
  };
};

Here, we call the delete function of httpClient module.

Backend

Here, we need to create a new API endpoint then use findOneAndDelete option provided by the mongoose package. The coding implementation is provided in the code snippet below:

router.delete("/pos_machine/:id", async (req, res) => {
  // console.log(req.params.id);
  try {
    let response = await POS_Machine.findOneAndDelete({ _id: req.params.id });

    res.json({
      result: "success",
      message: "Delete POS data Successfully",
    });
  } catch (err) {
    res.json({ result: "error", message: err.msg });
  }
});

Lastly, we need to create a function as the confirmation for the deletion of data. The implementation of the function is shown in the code snippet below

function confirmDelete(id) {
    swal({
      title: "Are you sure?",
      text: "Once deleted, you will not be able to recover this data!",
      icon: "warning",
      buttons: true,
      dangerMode: true,
    }).then((willDelete) => {
      if (willDelete) {
        dispatch(posmachineActions.remove(id));
        swal("Poof! Your POS Machine data has been deleted!", {
          icon: "success",
        });
      }
    });
  }

Now, we need to call the function when the user clicks on delete option as shown in the code snippet below:

return (
                            <tr key={index}>
                              <td>{data.alias}</td>
                              <td>{data.serial_number}</td>
                              <td>{data.created}</td>
                              <td>
                   <Link to={"/posmachine/update/" + data._id}>
                                  Edit
                                </Link>
                                {" | "}
                <Link onClick={() => confirmDelete(data._id)}>
                                  Delete
                                </Link>
                              </td>
                            </tr>
                          );
                        })
                      ) : (
                        <td></td>
                      )}
                    </tbody>

Hence, we can perform the delete operation now as shown in the simulation screenshot below:

Perform the Delete Pperationion
Perform the Delete Pperationion

Hence, we have successfully implemented the CRUD operation for the POS machine data.

Conclusion

In this chapter, we learned about the CRUD operations that can be performed in coordination between the frontend redux actions and the backend API endpoints. The routes and components were also created to add, display, update, and delete the POS machine data. Well, this chapter was a simple one. In the next chapter, we are going to step it up a notch by adding validations to the CRUD operations.

The code for this chapter is available in Github for Frontend and Backend.

Developer Relation @instamobile.io

Leave a Reply