How to create a simple React JS CRUD application?

In this post, I will share with you a React JS project which is a simple CRUD application.

The React JS CRUD application looks like the following image, and if you want to see a demo of the application, click on the Live Demo button.

screenshot of the simple react crud application

React JS CRUD application

This is a very simple and single-page application, here we will not make any API requests, we will simply save the data to the browser’s local storage.

Application folder structure

I installed my react development env through Vite. But, if you have installed react env via create-react-app then my main.jsx will be your index.js.

React CRUD application folder structure
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
)
import { useState, useEffect } from "react";
import "./App.css";

function App() {
    
    const [newUser, setNewUser] = useState({ name: "", email: "" });
    const [data, setData] = useState([]);
    const [edit, setEdit] = useState({
        isEdit: false,
        id: null,
    });
    
    useEffect(function(){
        const datax = localStorage.getItem('users')
        if (datax) setData(JSON.parse(datax));
    },[]);

    const saveToBrowser = (data) => {
        localStorage.setItem('users', JSON.stringify(data));
    }

    const onInputChange = (e, field) => {
        setNewUser({
            ...newUser,
            [field]: e.target.value,
        });
    };

    const areEmptyFields = () => {
        if (newUser.name.trim() == "" || newUser.email.trim() == "") {
            alert("Please fill all the required fields!");
            return true;
        }
        return false;
    };

    const formSubmit = (e) => {
        e.preventDefault();

        if (areEmptyFields()) return;
        if (edit.isEdit && edit.id) {
            for (const obj of data) {
                if (obj.id === edit.id) {
                    obj.name = newUser.name;
                    obj.email = newUser.email;
                    break;
                }
            }

            setEdit({ isEdit: false, id: null });
            setNewUser({ name: "", email: "" });
            saveToBrowser(data);
            return;
        }

        data.unshift({
            id: Date.now(),
            ...newUser,
        });
        setNewUser({ name: "", email: "" });
        saveToBrowser(data);
        e.target.reset();
    };

    const editUser = (id) => {
        const theUser = data.filter((usr) => usr.id === id);
        if (theUser[0]) {
            setNewUser({ name: theUser[0].name, email: theUser[0].email });
            setEdit({
                ...edit,
                isEdit: true,
                id,
            });
        }
    };

    const cancelEdit = () => {
        setEdit({ isEdit: false, id: null });
        setNewUser({ name: "", email: "" });
    };

    const delUser = (id) => {
        if (confirm("Are you sure?")) {
            let users = data.filter((usr) => usr.id !== id);
            setData(users);
            saveToBrowser(users);
        }
    };

    return (
        <div className="container">
            <h1>CRUD APP</h1>
            <form onSubmit={formSubmit}>
                <label htmlFor="userName">Name:</label>
                <input
                    type="text"
                    name="uname"
                    id="userName"
                    placeholder="Name"
                    autoComplete="off"
                    value={newUser.name}
                    onChange={(e) => onInputChange(e, "name")}
                />
                <label htmlFor="userEmail">Email:</label>
                <input
                    type="email"
                    name="uemail"
                    id="userEmail"
                    placeholder="Email"
                    autoComplete="off"
                    value={newUser.email}
                    onChange={(e) => onInputChange(e, "email")}
                />
                {edit.isEdit && edit.id ? (
                    <div className="edit-btn-wrap">
                        <button className="updateBtn" type="submit">
                            Update
                        </button>
                        <span className="cancelEdit" onClick={cancelEdit}>
                            Cancel
                        </span>
                    </div>
                ) : (
                    <button className="submit" type="submit">
                        Add User
                    </button>
                )}
            </form>

            <div className="users">
                <ul>
                    {data.map((item) => {
                        return (
                            <li className="profile" key={item.id}>
                                <span className="info">
                                    <span className="image">
                                        <img
                                            src={`https://robohash.org/${item.id}?size=100x100&bgset=bg1`}
                                            alt={item.name}
                                        />
                                    </span>
                                    <span className="name">
                                        {item.name}
                                        <br />
                                        {item.email}
                                    </span>
                                </span>
                                <span className="action">
                                    <button id="editBtn" onClick={() => editUser(item.id)}>
                                        Edit
                                    </button>
                                    <button id="delBtn" onClick={() => delUser(item.id)}>
                                        Delete
                                    </button>
                                </span>
                            </li>
                        );
                    })}
                </ul>
            </div>
        </div>
    );
}

export default App;
@import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=swap");

*,
*::after,
*::before {
    box-sizing: border-box;
}

:root {
    font-family: "Open Sans", sans-serif;
    font-size: 16px;
    line-height: 1.5em;
    font-weight: 400;

    color: #222222;
    --black-color: #202124;
    background-color: #f7f7f7;

    font-synthesis: none;
    text-rendering: optimizeLegibility;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    -webkit-text-size-adjust: 100%;
}
.container {
    max-width: 600px;
    margin: 20px auto;
    padding: 20px;
    background-color: white;
    border: 5px solid var(--black-color);
}

h1 {
    text-align: center;
    text-decoration: underline wavy;
}

.container form {
    padding: 10px 20px 20px;
    display: flex;
    flex-flow: column wrap;
}

form label {
    font-size: 18px;
    font-weight: bold;
    margin-top: 15px;
}
form label:first-child {
    margin-top: 0;
}
input,
button {
    all: unset;
    font-size: 1rem;
}
input[type="text"],
input[type="email"] {
    padding: 15px;
    border: 2px solid var(--black-color);
}

.updateBtn,
.cancelEdit,
.submit {
    margin-top: 20px;
    cursor: pointer;
    background: var(--black-color);
    color: white;
    padding: 15px;
    text-align: center;
    font-weight: bold;
}

.edit-btn-wrap{
    display: flex;
    align-items: center;
    gap: 20px;
    margin-top: 20px;
}
.cancelEdit,
.updateBtn{
    flex: 1;
    margin: 0;
}
.cancelEdit{
    background: #f1f1f1;
    color: #222222;

}

ul {
    list-style-type: none;
    padding: 0 20px;
}
.profile {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 20px;
}

.profile .info {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
}

.profile .image {
    border-radius: 50%;
    border: 3px solid var(--black-color);
    width: 50px;
    height: 50px;
    overflow: hidden;
    margin-right: 10px;
}

.profile img {
    width: 100%;
    height: 100%;
    transform: scale(1.5);
}
.profile .name {
    font-size: 14px;
    color: #555;
}
.profile .name::first-line {
    font-weight: bold;
    font-size: 20px;
    color: var(--black-color);
}
.profile .action {
    text-align: right;
    margin-right: -3px;
}
.profile .action button {
    border: 1px solid var(--black-color);
    margin: 3px;
    padding: 3px 7px;
    cursor: pointer;
    background-color: #f1f1f1;
}
#delBtn {
    border: 0;
    color: #555555;
}

@media (max-width: 550px) {
    .profile {
        background-color: #f7f7f7;
        padding: 20px 10px;
    }
    .profile,
    .profile .info {
        justify-content: center;
        flex-direction: column;
        text-align: center;
    }

    .profile .action button {
        font-size: 14px;
    }
}