Monday 17 July 2023

Create CRUD App in ReactJS with Spring Boot RestAPI pagination, searching and sorting features.

In this tutorial, we are going to build a React CRUD Application with Pagination, Searching and sorting features also consume Restful APIs developed in Spring boot.

We will do all Searching, sorting and pagination implementation by Spring Boot Restful API.

Let's start with creating a React App using create-react-app CLI.

To create a new app, you may choose one of the following methods:

Using npx
npx create-react-app react-appname

Using npm
npm init react-app react-appname



Install some npm packages:

npm install bootstrap reactstrap
npm install react-bootstrap bootstrap
npm install reactstrap react react-dom
npm install axios
npm install react-router-dom
npm install @material-ui/core
npm install @material-ui/lab



























------------------------------package.json----------------------------------

{
  "name": "react-pagination-sorting-searching-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@fortawesome/fontawesome-free": "^6.4.0",
    "@material-ui/core": "^4.12.4",
    "@material-ui/lab": "^4.0.0-alpha.61",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "axios": "^1.4.0",
    "bootstrap": "^5.3.0",
    "react": "^18.2.0",
    "react-bootstrap": "^2.8.0",
    "react-dom": "^18.2.0",
    "react-router-dom": "^6.14.1",
    "react-scripts": "5.0.1",
    "react-table": "^7.8.0",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}



--------------------------------app.js----------------------------------------

import React from "react";
import { Routes, Route ,Link} from "react-router-dom";
import "bootstrap/dist/css/bootstrap.min.css";
import "@fortawesome/fontawesome-free/css/all.css";
import "@fortawesome/fontawesome-free/js/all.js";
import "./App.css";
import Portfolio from "./components/Portfolio";
import AddPortfolio from "./components/AddPortfolio";
import EditPortfolio from "./components/EditPortfolio";


function App() {
  return (
    <div>
      <nav className="navbar navbar-expand navbar-dark bg-dark">
        <a href="/" className="navbar-brand">
          ramsis-code
        </a>
        <div className="navbar-nav mr-auto">
          <li className="nav-item">
            <Link to={"/"} className="nav-link">
             Fetch Portfolio
            </Link>
          </li>
          <li className="nav-item">
          <Link to={"/insert"} className="nav-link">
              Insert
          </Link>
          </li>
         
        </div>
      </nav>

      <div className="container mt-3">

       <Routes>
         <Route path='/' element={<Portfolio/>}></Route>
         <Route path='/insert' element={<AddPortfolio/>}></Route>
         <Route path='/edit/:id' element={<EditPortfolio/>}></Route>
         
       </Routes>
       
      </div>
    </div>
  );
}

export default App;



-------------------------------------AddPortfolio.js-------------------------------------

import React, { useState } from "react";
import PortfolioDataService from "../services/PortfolioService";

const AddPortfolio = () =>{

    const initialPortfolioState = {
        id: null,
        name: "",
        email: "",
        department: "",
        phone: "",
        city: ""
      };

      const [portfolio, setPortfolio] = useState(initialPortfolioState);
      const [submitted, setSubmitted] = useState(false);

        const [msgName, setMsgName] = useState("");
        const [msgEmail, setMsgEmail] = useState("");
        const [msgDept, setMsgDept] = useState("");
        const [msgCity, setMsgCity] = useState("");
        const [msgPhone, setMsgPhone] = useState("");
        const regexEmail = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;
      const handleInputChange = event => {
        const { name, value } = event.target;
        setPortfolio({ ...portfolio, [name]: value });
      };

      const savePortfolio = () => {
        var data = {
          id: portfolio.id,
          name: portfolio.name,
          email: portfolio.email,
          department: portfolio.department,
          phone: portfolio.phone,
          city: portfolio.city
        };

        if(data.name===''){
            setMsgName("Name is Required.");
            return false;
        }else{
            setMsgName('');
          }

          if(data.email===''){
            setMsgEmail("Email is Required.");
            return false;
          }else if(!regexEmail.test(data.email)){
            setMsgEmail("Invalid Email.");
            return false;
          } else{
            setMsgEmail('');
          }
         
          if(data.department===''){
            setMsgDept("Department is Required.");
            return false;
          }else{
            setMsgDept('');
          }

          if(data.phone===''){
            setMsgPhone("Phone is Required.");
            return false;
          }else{
            setMsgPhone('');
          }

          if(data.city===''){
            setMsgCity("City is Required.");
            return false;
          }else{
            setMsgCity('');
          }
     
     


        PortfolioDataService.create(data)
      .then(response => {
        setPortfolio({
          id: response.data.id,
          name: response.data.name,
          email: response.data.email,
          department: response.data.department,
          phone: response.data.phone,
          city: response.data.city
        });
        setSubmitted(true);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };

  const newPortfolio = () => {
    setPortfolio(initialPortfolioState);
    setSubmitted(false);
  };
  return (
  <div className="submit-form">

   {submitted ? (
        <div>
          <h5>You submitted successfully!</h5>
          <button className="btn btn-success" onClick={newPortfolio}>
            Add
          </button>
        </div>
      ) : (
        <div>
            <div className="form-group">
            <label htmlFor="name">Name</label>
            <input
              type="text"
              className="form-control"
              id="name"
              required
              value={portfolio.name}
              onChange={handleInputChange}
              name="name"
            />
            {msgName && <p style={{color: "red"}}>{msgName}</p>}
          </div>

          <div className="form-group">
            <label htmlFor="email">email</label>
            <input
              type="text"
              className="form-control"
              id="email"
              required
              value={portfolio.email}
              onChange={handleInputChange}
              name="email"
            />
              {msgEmail && <p style={{color: "red"}}>{msgEmail}</p>}
          </div>

          <div className="form-group">
            <label htmlFor="department">department</label>
            <input
              type="text"
              className="form-control"
              id="department"
              required
              value={portfolio.department}
              onChange={handleInputChange}
              name="department"
            />
             {msgDept && <p style={{color: "red"}}>{msgDept}</p>}
          </div>

          <div className="form-group">
            <label htmlFor="phone">phone</label>
            <input
              type="text"
              className="form-control"
              id="phone"
              required
              value={portfolio.phone}
              onChange={handleInputChange}
              name="phone"
            />
              {msgPhone && <p style={{color: "red"}}>{msgPhone}</p>}
          </div>

          <div className="form-group">
            <label htmlFor="city">city</label>
            <input
              type="text"
              className="form-control"
              id="city"
              required
              value={portfolio.city}
              onChange={handleInputChange}
              name="city"
            />
            {msgCity && <p style={{color: "red"}}>{msgCity}</p>}
          </div>
          <br/>
          <button onClick={savePortfolio} className="btn btn-success">
            Submit
          </button>  
        </div>
    )}
  </div>
   );

}

export default AddPortfolio

----------------------------------EditPortfolio.js ----------------------------------

import React, { useState, useEffect } from "react";
import PortfolioDataService from "../services/PortfolioService";
import { useParams } from "react-router-dom";

const  EditPortfolio = () =>{
    const {id} = useParams();
    const initialPortfolioState = {
        id: null,
        name: "",
        email: "",
        department: "",
        phone: "",
        city: ""
      };

      const [portfolio, setPortfolio] = useState(initialPortfolioState);
      const [submitted, setUpdate] = useState(false);
      const [message, setMessage] = useState("");

     const handleInputChange = event => {
        const { name, value } = event.target;
        setPortfolio({ ...portfolio, [name]: value });
      };

      const updatePortfolio = () => {
        var data = {
          id: portfolio.id,
          name: portfolio.name,
          email: portfolio.email,
          department: portfolio.department,
          phone: portfolio.phone,
          city: portfolio.city
        };

        PortfolioDataService.update(data.id,data)
      .then(response => {
        setMessage("The tutorial was updated successfully!");
        setUpdate(true);
        console.log(response.data);
      })
      .catch(e => {
        console.log(e);
      });
  };




  const newPortfolio = () => {
    setPortfolio(initialPortfolioState);
    setUpdate(false);
  };

      const getPortfolio = (id) => {
        PortfolioDataService.get(id)
          .then(response => {
            setMessage("The tutorial was updated successfully!");
            setPortfolio(response.data);
            console.log(response.data);
          })
          .catch(e => {
            console.log(e);
          });
      };

      useEffect(() => {
        getPortfolio(id);
      }, [id]);
   

      return (
        <div className="submit-form">
     
         {submitted ? (
              <div>
                <h6>Your form is updated successfully!</h6>
                <button className="btn btn-success" onClick={newPortfolio}>
                  Add
                </button>
              </div>
            ) : (
              <div>
                  <div className="form-group">
                  <label htmlFor="name">Name</label>
                  <input
                    type="text"
                    className="form-control"
                    id="name"
                    required
                    value={portfolio.name}
                    onChange={handleInputChange}
                    name="name"
                  />
                </div>
     
                <div className="form-group">
                  <label htmlFor="email">email</label>
                  <input
                    type="text"
                    className="form-control"
                    id="email"
                    required
                    value={portfolio.email}
                    onChange={handleInputChange}
                    name="email"
                  />
                </div>
     
                <div className="form-group">
                  <label htmlFor="department">department</label>
                  <input
                    type="text"
                    className="form-control"
                    id="department"
                    required
                    value={portfolio.department}
                    onChange={handleInputChange}
                    name="department"
                  />
                </div>
     
                <div className="form-group">
                  <label htmlFor="phone">phone</label>
                  <input
                    type="text"
                    className="form-control"
                    id="phone"
                    required
                    value={portfolio.phone}
                    onChange={handleInputChange}
                    name="phone"
                  />
                </div>
     
                <div className="form-group">
                  <label htmlFor="city">city</label>
                  <input
                    type="text"
                    className="form-control"
                    id="city"
                    required
                    value={portfolio.city}
                    onChange={handleInputChange}
                    name="city"
                  />
                </div>
                <br/>
                <button onClick={updatePortfolio} className="btn btn-success">
                  Update
                </button>  
              </div>
          )}
        </div>
         );

}

export default EditPortfolio;


---------------------------Portfolio.js--------------------------

import React, { useState, useEffect } from "react";
import Table from 'react-bootstrap/Table';
import Pagination from "@material-ui/lab/Pagination";
import PortfolioDataService from "../services/PortfolioService";
import {Link } from "react-router-dom";

const Portfolio = (props) => {
  const [portfolios, setPortfolios] = useState([]);
  const [searchName, setSearchName] = useState("");
  const [page, setPage] = useState(1);
  const [count, setCount] = useState(0);
  const [pageSize, setPageSize] = useState(5);
  const [pageSort, setPageSort] = useState("id,asc");
  const pageSizes = [5, 8, 10];
  const retrievePortfolios = () => {
        const params = getRequestParams(searchName, page, pageSize, pageSort);
        PortfolioDataService.getAll(params)
          .then((response) => {
            const { portfolios, totalPages } = response.data;
            console.log(portfolios);
            setPortfolios(portfolios);
            setCount(totalPages);
            console.log("param"+params);
            console.log(response.data);
          })
          .catch((e) => {
            console.log(e);
          });
      };

      const getPortfolio = () => {
        retrievePortfolios();
      }

      useEffect(retrievePortfolios, [page, pageSize,pageSort,searchName]);
     
      const handlePageChange = (event, value) => {
        setPage(value);
      };

     
      const deleteTutorial = (rowIndex) => {
   
        PortfolioDataService.remove(rowIndex)
          .then((response) => {
            getPortfolio();
          })
          .catch((e) => {
            console.log(e);
          });
      };
   
      const handlePageSizeChange = (event) => {
        setPageSize(event.target.value);
        setPage(1);
      };

   const handlePageSortClick = (event,value) =>{
        //alert(event.currentTarget.id);
        const sdir="asc";
        const sortString = event.currentTarget.id+","+sdir;
        setPageSort(sortString);
        retrievePortfolios();
     }
     
      const findByName = () => {
        setPage(1);
        retrievePortfolios();
      };

      const onChangeSearchName = (e) => {
        const searchName = e.target.value;
        setSearchName(searchName);
      };
   
      const getRequestParams = (searchName, page, pageSize, pageSort) => {
        let params = {};
   
        if (searchName) {
          params["name"] = searchName;
        }
   
        if (page) {
          params["page"] = page - 1;
        }
   
        if (pageSize) {
          params["size"] = pageSize;
        }

        if (pageSort) {
            params["sort"] = pageSort;
          }
   
        return params;
      };
      return (
   
   <>
      <div className="list row">
      <div className="col-md-8">
        <div className="input-group mb-3">
          <input
            type="text"
            className="form-control"
            placeholder="Search by title"
            value={searchName}
            onChange={onChangeSearchName}
          />
          <div className="input-group-append">
            <button
              className="btn btn-outline-secondary"
              type="button"
              onClick={findByName}
            >
              Search
            </button>
          </div>
        </div>
      </div>
      <div className="col-md-12 list">
      <div className="mt-6 my-3">
          {"Rows per page: "}
          <select onChange={handlePageSizeChange} value={pageSize}>
            {pageSizes.map((size) => (
              <option key={size} value={size}>
                {size}
              </option>
            ))}
          </select>
       </div>

 <div className="col-md-12 list">  
  <Table table table-striped table-bordered>
        <thead>
           <th><Link id="id" onClick={handlePageSortClick}>Id</Link></th>
           <th><Link id="name" onClick={handlePageSortClick}>Name</Link></th>
           <th><Link id="email" onClick={handlePageSortClick}>Email</Link></th>
           <th><Link id="department" onClick={handlePageSortClick}>Department</Link></th>
           <th><Link id="phone" onClick={handlePageSortClick}>Phone</Link></th>
           <th><Link id="city" onClick={handlePageSortClick}>City</Link></th>
           <th><Link>Action</Link></th>
        </thead>
        <tbody>

            {
            portfolios.map((data,index)=>(
           
            <tr>  
                <td>{data.id}</td>
                <td>{data.name}</td>                
                <td>{data.email}</td>
                <td>{data.department}</td>
                <td>{data.phone}</td>
                <td>{data.city}</td>
                <td>
             
             
                <Link to={"/edit/"+data.id} >
                 <i className="far fa-edit action fa-1x mx-2" style={{color: "green"}}></i>
                </Link>
               
               
                <span  onClick={() => deleteTutorial(data.id)}>
                  <i className="fas fa-trash action fa-1x mx-2"  style={{color: "red"}}></i>
                </span>
                </td>
            </tr>
                ))
            }
       </tbody>
       </Table>
       </div>
      </div>
      <div className="mt-6">
       <Pagination
            className="my-2"
            count={count}
            page={page}
            siblingCount={1}
            boundaryCount={1}
            variant="outlined"
            shape="rounded"
            onChange={handlePageChange}
          />
         </div>
         </div>  
    </>
   
    );
};

export default Portfolio;


-------------------------PortfolioService.js-------------------------

import http from "../http-common";

const getAll = (params) => {
    return http.get("/portfolios", { params });
  };

const create = (data) => {
  return http.post("/portfolios", data);
};  

const remove = (id) => {
  return http.delete(`/portfolios/${id}`);
};

const get = (id) => {
  return http.get(`/portfolios/${id}`);
};

const update = (id, data) => {
  return http.put(`/portfolios/${id}`, data);
};

  const PortfolioService = {
    getAll,
    create,
    remove,
    get,
    update,
  };
 
  export default PortfolioService;

-----------------------------http-common.js----------------------------

import axios from "axios";

export default axios.create({
  baseURL: "http://localhost:9090/api",
  headers: {
    "Content-type": "application/json"
  }
});