How To Develop A CRUD App With ReactJS And Redux Toolkit

How To Develop A CRUD App With ReactJS And Redux Toolkit

Avatar photoPosted by

Hi there, today we will be developing a CRUD app with ReactJS and Redux Toolkit. The four basic operations in any type of programming are CRUD. CRUD is an acronym for CREATE, READ, UPDATE, DELETE.

React or also called React.js or Reactjs is a free and open-source JavaScript library used for building user interfaces(UI). It is one of the most popular JavaScript libraries for building the front end. React is created by Facebook and maintained by Facebook.

Redux Toolkit (RTK) is the official, recommended toolset for efficient Redux development. It provides a set of utilities that simplify the process of writing Redux logic and aims to reduce boilerplate and improve the developer experience. RTK includes features like simplified store setup, pre-configured middleware, and utilities for creating slices, reducers, and actions in a more concise manner.

In this tutorial, we will be using the REST API available that is made by Binaryboxtus. you can check it out here.

Prerequisite:

  • npm >= 7.24.0
  • node >= v16.10.0
  • npx >= 7.24.0

Step 1: Create A React App

First, select a folder that you want the React App to be created then execute this command on Terminal or CMD to create the React App:

npx create-react-app react-crud-app

Step 2: Install packages

After creating a fresh react project, go to the react project folder and install these packages:

  • react-router-dom – a package used for client-side routing which allows the app to change the URL without making another request for a document from the server but instead it will immediately render the new UI or new information.
  • bootstrap – a package of bootstrap framework
  • sweetalert2 – a package for pop-up boxes
  • @reduxjs/toolkit – a package, commonly referred to as Redux Toolkit (RTK), is the official, recommended way to write Redux logic. It was designed to simplify the process of using Redux and address several issues that developers encountered with the original Redux implementation, such as boilerplate code and complex configuration.
  • react-redux package provides official bindings for integrating Redux, a state management library, with React, a popular JavaScript library for building user interfaces. This package allows React components to interact with the Redux store, enabling them to read from and dispatch actions to the store, thus facilitating state management across the entire application
npm i bootstrap
npm i react-router-dom
npm i sweetalert2
npm i @reduxjs/toolkit 
npm i react-redux

Step 3: Create .env

After installing the packages, we will now create our .env file on our root directory. We will declare the API base URL and API Key in the .env file. Don’t forget to add it to the .gitignore file.

You can get the REACT_APP_API_KEY here. Click the “Reveal Key” button and copy the API key.

image 19 Binaryboxtuts

.env

REACT_APP_API_URL="https://mock-api.binaryboxtuts.com/"
REACT_APP_API_KEY="put_the_api_key_here"

Step 4: API Slice, Routing and Components

We will now create the API slice, routing, and components. Refer to the image below for the file structure.

image 50 Binaryboxtuts

You can create your own file structure but for this tutorial just follow along.

Let’s create a new directory /src/api. Inside the directory, let’s create a file named apiSlice.js and add these lines of codes:

src/api/apiSlice.js

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export const apiSlice = createApi({
    reducerPath: 'api',
    baseQuery: fetchBaseQuery(
        {
            baseUrl: process.env.REACT_APP_API_URL,
            prepareHeaders: (headers) => {
                headers.set("X-Binarybox-Api-Key", process.env.REACT_APP_API_KEY)
                return headers;
            },
        },
    ),
    tagTypes: ['Projects'],
    endpoints: (builder) => ({
        getProjects: builder.query({
            query: () => '/api/projects',
            providesTags: ['Projects']
        }),
        getProject: builder.query({
            query: ({ id }) => `/api/projects/${id}`,
        }),
        addProject: builder.mutation({
            query: (project) => ({
                url: '/api/projects',
                method: 'POST',
                body: project
            }),
            invalidatesTags: ['Projects']
        }),
        updateProject: builder.mutation({
            query: (project) => ({
                url: `/api/projects/${project.id}`,
                method: 'PATCH',
                body: project
            }),
            invalidatesTags: ['Projects']
        }),
        deleteProject: builder.mutation({
            query: ({ id }) => ({
                url: `/api/projects/${id}`,
                method: 'DELETE',
            }),
            invalidatesTags: ['Projects']
        }),
    })
})

export const {
    useGetProjectsQuery,
    useGetProjectQuery,
    useAddProjectMutation,
    useUpdateProjectMutation,
    useDeleteProjectMutation
} = apiSlice

Let’s update the App.js file – We will add the routes here:

src/App.js

import React from 'react'
import { BrowserRouter as Router, Routes, Route } from "react-router-dom"
import ProjectList from "./pages/ProjectList"
import ProjectCreate from "./pages/ProjectCreate"
import ProjectEdit from "./pages/ProjectEdit"
import ProjectShow from "./pages/ProjectShow"
 
function App() {
  return (
    <Router>
      <Routes>
          <Route exact path="/"  element={<ProjectList/>} />
          <Route path="/create"  element={<ProjectCreate/>} />
          <Route path="/edit/:id"  element={<ProjectEdit/>} />
          <Route path="/show/:id"  element={<ProjectShow/>} />
      </Routes>
    </Router>
  );
}
 
export default App;

Let’s update the index.js file:

src/index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import 'bootstrap/dist/css/bootstrap.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { ApiProvider } from "@reduxjs/toolkit/query/react";
import { apiSlice } from "./api/apiSlice";

const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
  <React.StrictMode>
    <ApiProvider api={apiSlice}>
      <App />
    </ApiProvider>
  </React.StrictMode>
);
 
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

Let’s create a new directory /src/components. Inside the directory, let’s create a file named Layout.js – this will serve as a template.

src/components/Layout.js

import React from 'react'
 
const Layout =({children}) =>{
    return(
        <div className="container">{children}</div>
    )
}
 
export default Layout;

We will now create a directory src/pages. Inside the folder let’s create these files for our pages:

  • ProjectCreate.js
  • ProjectEdit.js
  • ProjectList.js
  • ProjectShow.js

src/pages/ProjectCreate.js

import React, {useState, useEffect} from 'react'
import { Link } from "react-router-dom"
import Swal from 'sweetalert2'
import Layout from "../components/Layout"
import { useAddProjectMutation } from '../api/apiSlice'
 
function ProjecCreate() {
    const [name, setName] = useState('');
    const [description, setDescription] = useState('')
    const [addProject, { isLoading, isSuccess, isError }] = useAddProjectMutation()

    const handleSave = () => {
        addProject({
            name: name,
            description: description
        })
    }
  
    useEffect(()=>{
        if(isSuccess){
            Swal.fire({
                icon: 'success',
                title: 'Project saved successfully!',
                showConfirmButton: false,
                timer: 1500
            })
            setName('')
            setDescription('')
        }
    },[isSuccess])

    useEffect(()=>{
        if(isError){
            Swal.fire({
                icon: 'error',
                title: 'An Error Occured!',
                showConfirmButton: false,
                timer: 1500
            })
        }
    },[isError])

    return (
        <Layout>
            <div className="container">
                <h2 className="text-center mt-5 mb-3">Create New Project</h2>
                <div className="card">
                    <div className="card-header">
                        <Link 
                            className="btn btn-outline-info float-right"
                            to="/">View All Projects
                        </Link>
                    </div>
                    <div className="card-body">
                        <form>
                            <div className="form-group">
                                <label htmlFor="name">Name</label>
                                <input 
                                    onChange={(event)=>{setName(event.target.value)}}
                                    value={name}
                                    type="text"
                                    className="form-control"
                                    id="name"
                                    name="name"/>
                            </div>
                            <div className="form-group">
                                <label htmlFor="description">Description</label>
                                <textarea 
                                    value={description}
                                    onChange={(event)=>{setDescription(event.target.value)}}
                                    className="form-control"
                                    id="description"
                                    rows="3"
                                    name="description"></textarea>
                            </div>
                            <button 
                                disabled={isLoading}
                                onClick={handleSave} 
                                type="button"
                                className="btn btn-outline-primary mt-3">
                                Save Project
                            </button>
                        </form>
                    </div>
                </div>
            </div>
        </Layout>
    );
}
  
export default ProjecCreate;

src/pages/ProjectEdit.js

import React, { useState, useEffect } from 'react'
import { Link, useParams } from "react-router-dom"
import Swal from 'sweetalert2'
import { useGetProjectQuery , useUpdateProjectMutation } from '../api/apiSlice'
import Layout from "../components/Layout"
 
function ProjectEdit() {
    const id = useParams().id
    const [name, setName] = useState('');
    const [description, setDescription] = useState('')
    const {data:project = {}, isSuccess, status } = useGetProjectQuery({id: id})
    const [updateProject, { isLoading, isSuccess:IsUpdateSuccess, isError }] = useUpdateProjectMutation()

    useEffect(()=>{
        if(status && isSuccess){
            setName(project?.name)
            setDescription(project?.description)
        }
    },[status, isSuccess])
  
    const handleSave = () => {
        updateProject({id:id, name:name, description:description})
    }
  
    useEffect(()=>{
        if(IsUpdateSuccess){
            Swal.fire({
                icon: 'success',
                title: 'Project updated successfully!',
                showConfirmButton: false,
                timer: 1500
            })
            setName('')
            setDescription('')
        }
    },[IsUpdateSuccess])

    useEffect(()=>{
        if(isError){
            Swal.fire({
                icon: 'error',
                title: 'An Error Occured!',
                showConfirmButton: false,
                timer: 1500
            })
        }
    },[isError])
  
    return (
        <Layout>
            <div className="container">
                <h2 className="text-center mt-5 mb-3">Edit Project</h2>
                <div className="card">
                    <div className="card-header">
                        <Link 
                            className="btn btn-outline-info float-right"
                            to="/">View All Projects
                        </Link>
                    </div>
                    <div className="card-body">
                        <form>
                            <div className="form-group">
                                <label htmlFor="name">Name</label>
                                <input 
                                    onChange={(event)=>{setName(event.target.value)}}
                                    value={name}
                                    type="text"
                                    className="form-control"
                                    id="name"
                                    name="name"/>
                            </div>
                            <div className="form-group">
                                <label htmlFor="description">Description</label>
                                <textarea 
                                    value={description}
                                    onChange={(event)=>{setDescription(event.target.value)}}
                                    className="form-control"
                                    id="description"
                                    rows="3"
                                    name="description"></textarea>
                            </div>
                            <button 
                                disabled={isLoading}
                                onClick={handleSave} 
                                type="button"
                                className="btn btn-outline-success mt-3">
                                Update Project
                            </button>
                        </form>
                    </div>
                </div>
            </div>
        </Layout>
    );
}
  
export default ProjectEdit;

src/pages/ProjectList.js.

import React,{ useEffect} from 'react'
import { Link } from "react-router-dom"
import Swal from 'sweetalert2'
import Layout from "../components/Layout"
import {
    useGetProjectsQuery,
    useDeleteProjectMutation
} from "../api/apiSlice"
 
  
function ProjectList() {
    const { data:projects = [] } = useGetProjectsQuery()
    const [deleteProject, { isSuccess, isError }] = useDeleteProjectMutation()

    const handleDelete = (id) => {
        Swal.fire({
            title: 'Are you sure?',
            text: "You won't be able to revert this!",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: 'Yes, delete it!'
          }).then((result) => {
            if (result.isConfirmed) {
                deleteProject({id:id})
            }
          })
    }

    useEffect(()=>{
        if(isSuccess){
            Swal.fire({
                icon: 'success',
                title: 'Project deleted successfully!',
                showConfirmButton: false,
                timer: 1500
            })
        }
    },[isSuccess])

    useEffect(()=>{
        if(isError){
            Swal.fire({
                icon: 'error',
                title: 'An Error Occured!',
                showConfirmButton: false,
                timer: 1500
            })
        }
    },[isError])
  
    return (
        <Layout>
           <div className="container">
            <h2 className="text-center mt-5 mb-3">Project Manager</h2>
                <div className="card">
                    <div className="card-header">
                        <Link 
                            className="btn btn-outline-primary"
                            to="/create">Create New Project
                        </Link>
                    </div>
                    <div className="card-body">
              
                        <table className="table table-bordered">
                            <thead>
                                <tr>
                                    <th>Name</th>
                                    <th>Description</th>
                                    <th width="240px">Action</th>
                                </tr>
                            </thead>
                            <tbody>
                                {projects.map((project, key)=>{
                                    return (
                                        <tr key={key}>
                                            <td>{project.name}</td>
                                            <td>{project.description}</td>
                                            <td>
                                                <Link
                                                    to={`/show/${project.id}`}
                                                    className="btn btn-outline-info mx-1">
                                                    Show
                                                </Link>
                                                <Link
                                                    className="btn btn-outline-success mx-1"
                                                    to={`/edit/${project.id}`}>
                                                    Edit
                                                </Link>
                                                <button 
                                                    onClick={()=>handleDelete(project.id)}
                                                    className="btn btn-outline-danger mx-1">
                                                    Delete
                                                </button>
                                            </td>
                                        </tr>
                                    )
                                })}
                            </tbody>
                        </table>
                    </div>
                </div>
            </div>
        </Layout>
    );
}
  
export default ProjectList;

src/pages/ProjectShow.js

import React, {useState, useEffect} from 'react';
import { Link, useParams } from "react-router-dom";
import { useGetProjectQuery } from '../api/apiSlice'
import Layout from "../components/Layout"
 
  
function ProjectShow() {
    const id = useParams().id
    const {data:project = {}} = useGetProjectQuery({id: id})
  
    return (
        <Layout>
           <div className="container">
            <h2 className="text-center mt-5 mb-3">Show Project</h2>
                <div className="card">
                    <div className="card-header">
                        <Link 
                            className="btn btn-outline-info float-right"
                            to="/"> View All Projects
                        </Link>
                    </div>
                    <div className="card-body">
                        <b className="text-muted">Name:</b>
                        <p>{project?.name}</p>
                        <b className="text-muted">Description:</b>
                        <p>{project?.description}</p>
                    </div>
                </div>
            </div>
        </Layout>
    );
}
  
export default ProjectShow;

Step 5: Run the app

We’re all done, what is left is to run the app.

npm start

Open this URL:

http://localhost:3000

Screenshots:

Index Page

How To Develop ReactJS CRUD App using a REST API

Create Page

How To Develop ReactJS CRUD App using a REST API

Edit Page

How To Develop ReactJS CRUD App using a REST API

Show Page

How To Develop ReactJS CRUD App using a REST API