Beejartha

Categories
Node & React Js

ReactJS CRUD Application

In this blog post, I will show you how to build a simple to-do application using React.js. React.js is a popular JavaScript library for creating user interfaces with reusable components. You will learn how to use React hooks, state management, and event handling to create a functional and interactive app.
The app will have the following features:
– Display a list of tasks that the user can add, edit, delete, or mark as completed.
– Filter the tasks by all, active, or completed status.
– Store the tasks in our local storage so that they persist across sessions. To do that we are going to use a package called json-server that’s going to create a localized database.
To follow along, you will need some basic knowledge of HTML, CSS, and JavaScript, as well as Node.js and npm installed on your machine. You will also need a code editor of your choice.
Let’s get started by creating a new React project using the create-react-app tool:
First you need to create a react application. You do this by adding this bash script to your terminal;

npx create-react-app react-todo

This will create a new folder called react-todo with all the necessary files and dependencies for a React app. Navigate to the project folder and start the development server:

cd react-todo
npm start

You should see a default React app running on http://localhost:3000 in your browser.
Next, let’s clean up some of the files that we don’t need for this project. Open the src folder and delete the following files:

- App.test.js
- index.css
- logo.svg
- reportWebVitals.js
- setupTests.js

Also, remove the corresponding import statements from App.js and index.js.
Add the following packages to use in the application.


yarn add moment node-sass redux redux-thunk uuid react-loading
Yarn add -D autoprefixer json-server concurrently postcss postcss-cli tailwindcss sass

Now, let’s add some basic styling to our app. At style.css in the src folder and paste the following code:


@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--primary-color: #17a2b8;
--dark-color: #343a40;
--light-color: #f4f4f4;
--danger-color: #dc3545;
--success-color: #28a745;
}
html {
font-size: 12px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}
body {
margin: 0;
padding: 0;
font-family: "Open Sans", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow-x: hidden;
overflow-y: auto;
color: #333;
}
ul {
list-style-type: none;
}
a {
text-decoration: none;
}
h1 {
@apply text-2xl;
}
h2 {
@apply text-xl;
}

This will add some basic styles to our app components. Don’t forget to import


import './App.css';

Now that we have set up our project and added some styles, let’s start building our React components. In the src folder lets create these folders and files


-Actions
Alert.js
Todo.js
-Components
AddTodo.js
Alert.js
EditTodo.js
Footer.js
Todo.js
NavBar.js
-Reducers
Alert.js
Index.js
Todo.js
-Constants
Types.js
App.js
Index.js
store.js

Lets create our action types that we are going to use in this project. At types.js add these;


export const SET_ALERT = "SET_ALERT";
export const REMOVE_ALERT = "REMOVE_ALERT";
export const ADD_TODO = "ADD_TODO";
export const GET_TODO = "GET_TODO";
export const DELETE_TODO = "DELETE_TODO";
export const UPDATE_TODO = "UPDATE_TODO";
export const CLEAR_TODO = "CLEAR_TODO";
export const SEARCH_TODO = "SEARCH_TODO";
export const TODO_ERROR = "TODO_ERROR";
export const SET_CURRENT = "SET_CURRENT";
export const CLEAR_CURRENT = "CLEAR_CURRENT";
export const SET_LOADING = "SET_LOADING";

In our reducers folder at todo.js file you created lets use our action types to manage all states that are going to be used for our to-do application. Also not forgeting to import our action types that we are going to use in our switch case statement. Write the following code:


import {
ADD_TODO,
UPDATE_TODO,
CLEAR_TODO,
CLEAR_CURRENT,
GET_TODO,
SEARCH_TODO,
SET_CURRENT,
SET_LOADING,
TODO_ERROR,
DELETE_TODO,
} from "../constants/types";
const initialState = {
todos: null,
current: null,
loading: false,
error: null,
};
export default (state = initialState, action) => {
const { type, payload } = action;
switch (type) {
case GET_TODO:
return {
...state,
todos: payload,
loading: false,
};
case ADD_TODO:
return {
...state,
todos: [...state.todos, payload],
loading: false,
};
case UPDATE_TODO:
return {
...state,
todos: state.todos.map((todo) =>
todo.id === payload.id ? payload : todo
),
};
case DELETE_TODO:
return {
...state,
todos: state.todos.filter((todo) => todo.id !== payload),
loading: false,
};
case SEARCH_TODO:
return {
...state,
todos: payload,
};
case SET_CURRENT:
return {
...state,
current: payload,
};
case CLEAR_CURRENT:
return {
...state,
current: null,
};
case SET_LOADING:
return {
...state,
loading: true,
};
case TODO_ERROR:
console.error(payload);
return {
...state,
error: payload,
};
default:
return state;
}
};

At alert.js in reducers folder write the following code:


import { SET_ALERT, REMOVE_ALERT } from "../constants/types";
const initialState = [];
export default function (state = initialState, action) {
const { type, payload } = action;
switch (type) {
case SET_ALERT:
return [...state, payload];
case REMOVE_ALERT:
return state.filter((alert) => alert.id !== payload);
default:
return state;
}
}

Then we need to combine all the reducers created. Here it is best we use one of redux’s methods:

CombineReducer


import { combineReducers } from "redux";

This is going to combine all the reducers created so that all the states can be recognized in our application through store.

Since i’ve mentioned a store, let me explain what it’s functionality;
A Store in Redux is like a placeholder for all the states, and states mutations. If any change of state occurs in our application the store will recognize it and stores. Lets continue;
Create a file index.js and write the code below;


import { combineReducers } from "redux";
import alert from "./alert";
import todo from "./todo";
export default combineReducers({
todo,
alert,
});


Redux Core principles

Lets make our Actions, Actions in redux are responsible for state change. This is done by emitting or dispatch an action. Create a file called alert.js write the code below;


import { v4 as uuidv4 } from "uuid";
import { SET_ALERT, REMOVE_ALERT } from "../constants/types";
export const setAlert = (msg, alertType, timeout = 3000) => (dispatch) => {
const id = uuidv4();
dispatch({
type: SET_ALERT,
payload: { msg, alertType, id },
});
setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
};

The above code will be responsible for managing alert notification action. Also note we’ve used a package called uuid. This will set unique ID identifiers for every alert shown in our application. Next Create a file called todo.js.

Todo.js


import { setAlert } from "./alert";
import {
ADD_TODO,
UPDATE_TODO,
CLEAR_TODO,
CLEAR_CURRENT,
GET_TODO,
SEARCH_TODO,
SET_CURRENT,
SET_LOADING,
TODO_ERROR,
DELETE_TODO,
} from "../constants/types";
//ADD TODO
export const addTodo = (todo) => async (dispatch) => {
try {
setLoading();
const res = await fetch("/todos", {
method: "POST",
body: JSON.stringify(todo),
headers: { "Content-Type": "application/json" },
});
const data = await res.json();
dispatch({
type: ADD_TODO,
payload: data,
});
} catch (err) {
dispatch({
type: TODO_ERROR,
payload: err.response.statusText,
});
}
};
//GET TODO
export const getTodo = () => async (dispatch) => {
try {
setLoading();
const res = await fetch("/todos");
const data = await res.json();
dispatch({
type: GET_TODO,
payload: data,
});
} catch (err) {
dispatch({
type: TODO_ERROR,
payload: err.response.statusText,
});
}
};
//UPDATE TODO
export const updateTodo = (todo) => async (dispatch) => {
try {
setLoading();
const res = await fetch(`/todos/${todo.id}`, {
method: "PUT",
body: JSON.stringify(todo),
headers: {
"Content-Type": "application/json",
},
});
const data = await res.json();
dispatch({
type: UPDATE_TODO,
payload: data,
});
} catch (err) {
dispatch({
type: TODO_ERROR,
payload: err.response.statusText,
});
}
};
//DELETE TODO
export const deleteTodo = (id) => async (dispatch) => {
try {
setLoading();
await fetch(`/todos/${id}`, {
method: "DELETE",
});
dispatch({
type: DELETE_TODO,
payload: id,
});
} catch (err) {
dispatch({
type: TODO_ERROR,
payload: err.response.statusText,
});
}
};
//SEARCH TODO
export const searchTodo = (text) => async (dispatch) => {
try {
setLoading();
const res = await fetch(`/todos?q=${text}`);
const data = await res.json();
dispatch({
type: SEARCH_TODO,
payload: data,
});
} catch (err) {
dispatch({
type: TODO_ERROR,
payload: err.response.statusText,
});
}
};
//SET LOADING
export const setLoading = () => {
return {
type: SET_LOADING,
};
};
//SET CURRENT TODO
export const setCurrent = (todo) => {
return {
type: SET_CURRENT,
payload: todo,
};
};
//CLEAR CURRENT TODO
export const clearCurrent = () => {
return {
type: CLEAR_CURRENT,
};
};

This file will manage all of our to-do action requests, i.e. Creation, Deletion, Update and Reading of our To-do applications. You can note how we are reusing the same action types used in our reducers.
Now lets create our components that we are going to use in our application.

AddTodo.js

This is responsible for the creation of our task, We’ll trigger addTodo action, to create our application.


import React, { useState } from "react";
import { connect } from "react-redux";
import { addTodo } from "../actions/todo";
import { setAlert } from "../actions/alert";
const required = (value) => {
if (!value) { return
This field is required!;}};
const AddTodo = ({ addTodo, setAlert }) => {
const [message, setMessage] = useState("");
const onSubmit = (e) => {
e.preventDefault();
if (message === "" || null) {
setAlert("This field is required!", "danger");
} else {
const newTodo = {
message,
date: new Date(),
};
addTodo(newTodo);
setAlert("Item successfully added", "success");
setMessage("");
}
};
return (
<section readonly className="mx-auto flex w-full max-w-7xl flex-col gap-2 rounded-md bg-white px-4 py-2 shadow-md transition-all duration-300 hover:scale-105 hover:border-l-2 hover:border-r-2 hover:border-solid hover:border-green-500">
<h1 readonly className="my-2 text-xl font-bold">Add</h1> <div readonly className="w-full"> <input readonly type="text" name="message" placeholder="Add Task" value={message} onChange={(e) => setMessage(e.target.value)}
className={`h-10 w-full rounded-md px-4 py-2 text-gray-800 ring-1 ring-gray-200 focus:border-gray-300 focus:outline-none focus:ring-1 focus:ring-gray-300 `}
/>
</div>
<div className="flex-end flex justify-end">
<button
type="submit"
form="add-todo"
onClick={onSubmit}
className={`flex h-10 w-20 items-center justify-center rounded-md bg-green-500 px-4 py-3 font-semibold text-white duration-200 ease-in hover:scale-105 hover:cursor-pointer `}
disabled={message === ""}
>
Add
</button>
</div>
</section>
);
};
export default connect(null, { setAlert, addTodo })(AddTodo);

Alert.js

This is going to manage all of our alert being triggered.


import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
const Alert = ({ alerts }) =>
alerts !== null &&
alerts.length > 0 &&
alerts.map((alert) => (
<div
key={alert.id}
className={`alert absolute right-4 top-0 z-50 rounded-md alert-${alert.alertType}`}
>
{alert.msg}
</div>
));
Alert.propTypes = {
alerts: PropTypes.array.isRequired,
};
const mapStateToProps = (state) => ({
alerts: state.alert,
});
export default connect(mapStateToProps)(Alert);

EditTodo.js

This is responsible for updating any item created in our list.


import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { setAlert } from "../actions/alert";
import { updateTodo } from "../actions/todo";
const required = (value) => {
if (!value) {
return <p className="text-danger">This field is required!</p>;
}
};
const EditTodo = ({ current, updateTodo, setAlert }) => {
const [message, setMessage] = useState("");
const [attention, setAttention] = useState(false);
useEffect(() => {
if (current) {
setMessage(current.message);
setAttention(current.attention);
}
}, [current]);
const Clear = () => {
setMessage("");
setAttention(false);
};
const onSubmit = (e) => {
e.preventDefault();
if (message === "") {
setAlert("This field is required!", "danger");
} else {
const editTodo = {
id: current.id,
message,
attention,
date: new Date(),
};
updateTodo(editTodo);
setAlert(`${message} Updated Successfully`, "success");
setMessage("");
setAttention(false);
}
};
return (
<section
className={`mx-auto flex w-full max-w-7xl flex-col gap-2 rounded-md bg-white px-4 py-2 shadow-md transition-all duration-300 hover:scale-105 hover:border-l-2 hover:border-r-2 hover:border-solid hover:border-blue-400 ${
message
? " scale-105 border-l-2 border-r-2 border-solid border-blue-400 transition-all duration-300"
: "transition-all duration-300"
}`}
>
<h1 className="text-xl font-bold">Edit </h1>
<div className="w-full">
<input
type="text"
name="message"
placeholder="Edit Task"
value={message}
onChange={(e) => setMessage(e.target.value)}
className={`h-10 w-full rounded-md px-4 py-2 text-gray-800 ring-1 ring-gray-200 focus:border-gray-300 focus:outline-none focus:ring-1 focus:ring-gray-300 `}
/>
</div>
<div className="flex-end flex items-center justify-between">
<label className="flex items-center space-x-2 font-medium text-gray-600">
<input
type="checkbox"
className="filled-in"
checked={attention}
value={attention}
onChange={(e) => setAttention(!attention)}
/>{" "}
<span>Check</span>
</label>
<div className="flex items-center space-x-2">
<button
type="button"
onClick={Clear}
className={`flex h-10 w-full items-center justify-center rounded-md bg-red-300 px-4 py-3 font-semibold text-white duration-200 ease-in hover:scale-105 hover:cursor-pointer `}
>
Cancel
</button>
<button
type="submit"
form="add-todo"
onClick={onSubmit}
className={`flex h-10 w-full items-center justify-center rounded-md bg-cyan-400 px-4 py-3 font-semibold text-white duration-200 ease-in hover:scale-105 hover:cursor-pointer `}
>
Submit
</button>
</div>
</div>
</section>
);
};
const mapStateToProps = (state) => ({
current: state.todo.current,
});
export default connect(mapStateToProps, { setAlert, updateTodo })(EditTodo);

Todo.js

This is responsible for showing all items in our list and filtering any item available.


import React, { useEffect, useRef } from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import Moment from "react-moment";
import moment from "moment";
import { setAlert } from "../actions/alert";
import { getTodo, deleteTodo, setCurrent, searchTodo } from "../actions/todo";
import ReactLoading from "react-loading";
const Loading = () => ;
const Todo = ({
getTodo,
setAlert,
searchTodo,
deleteTodo,
setCurrent,
todo: { todos, loading },
}) => {
const text = useRef("");
useEffect(() => {
getTodo();
}, []);
const onChange = (e) => {
searchTodo(text.current.value);
};
const OnDelete = async (todo) => {
deleteTodo(todo.id);
setAlert("Item Removed", "success");
};
if (loading || todos === null) {
return (
<div className="loading mx-auto w-full">
<Loading />
</div>
);
}
return (
<div>
<div className="mx-auto w-full max-w-7xl rounded-lg border border-gray-200 bg-white shadow-lg">
<header className="flex w-full items-center justify-between border-b border-gray-100 px-5 py-4">
<h2 className="font-semibold text-gray-800">Task(s)</h2>
<section className="flex w-full max-w-lg flex-row space-x-2">
<button
title="refresh"
type="button"
onClick={getTodo}
className="items-center rounded-md bg-gray-400 px-3 py-2 font-semibold text-white"
><i className="fas fa-sync-alt"/>
</button>
<div className="w-full">
<input
id="search"
type="search"
placeholder="Search..."
ref={text}
onChange={onChange}
className={`h-10 w-full rounded-md px-4 py-2 text-gray-800 ring-1 ring-gray-200 focus:border-gray-300 focus:outline-none focus:ring-1 focus:ring-gray-300 `}
/>
</div>
</section>
</header>
<div className="p-3">
<div className="overflow-x-auto">
{!loading && todos.length <= 0 ? ( <p className="mx-auto flex w-full max-w-lg flex-col text-lg font-bold text-blue-400"> Task list is empty, please add some info </p> ) : ( <table className="table w-full table-auto"> <thead className="bg-gray-100 text-base font-semibold uppercase leading-normal text-gray-400"> <tr><th className="whitespace-nowrap p-2"> <div className="text-left font-semibold">#</div>
</th>
<th className="whitespace-nowrap p-2">
<div className="text-left font-semibold">To-Do</div>
</th>
<th className="whitespace-nowrap p-2">
<div className="text-left font-semibold">Time</div>
</th>
<th className="whitespace-nowrap p-2">
<div className="text-left font-semibold">Action</div>
</th>
</tr>
</thead>
{todos.map((todo) => (
<tbody className=" font-light text-gray-600">
<tr className="border-b border-gray-200 hover:bg-gray-50">
<td className="whitespace-nowrap p-2">
<div className="text-left">{todo.id}</div>
</td>
<td className="whitespace-nowrap p-2">
<div
className={`text text-left ${
todo.attention
? "text-success line-through"
: "text-gray-600 "
}`}
>
{todo.message}
</div>
</td>
<td className="whitespace-nowrap p-2">
<div
className={`text text-left ${
todo.attention
? "text-success line-through"
: "text-gray-600 "
}`}
>
<Moment format="DD-MMM-YYYY hh:mm a">
{moment.utc(todo.date)}
</Moment>
</div>
</td>
<td className="whitespace-nowrap p-2">
<div className="flex items-center gap-2 text-left">
<button
title="Edit"
type="button"
onClick={() => setCurrent(todo)}
className="items-center rounded-md bg-cyan-400 px-3 py-3 font-semibold text-white"
>
<i className="fa fa-edit" />
</button>
<button
title="Delete"
type="button"
onClick={() => OnDelete(todo)}
className="items-center rounded-md bg-red-300 px-3 py-3 font-semibold text-white"
>
<i className="fa fa-trash" />
</button>
</div>
</td>
</tr>
</tbody>
))}
</table>
)}
</div>
</div>
</div>
</div>
);
};
Todo.propTypes = {
getTodo: PropTypes.func.isRequired,
todo: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
todo: state.todo,
});
export default connect(mapStateToProps, {
getTodo,
setAlert,
deleteTodo,
setCurrent,
searchTodo,
})(Todo);

NavBar.js


import React from "react";
const NavBar = () => {
return (
<nav className="flex w-full flex-col bg-white px-6 py-4 text-center font-sans shadow sm:flex-row sm:items-baseline sm:justify-between sm:text-left">
<div className="mb-2 sm:mb-0">
<a href="/"className="text-grey-darkest hover:text-blue-dark text-2xl font-semibold no-underline">
To-Do Application
</a>
</div>
</nav>
);
};
export default NavBar;

Footer.js


import React from "react";
const Footer = () => {
return (
<footer className="absolute bottom-2 mt-10 flex flex-1 flex-col px-6 py-2">
<p className="themeText text-start text-lg font-normal md:text-left">
(c) {new Date().getFullYear()}{" "}
<a
href="/"
className={`text-xl font-normal text-cyan-600 hover:text-blue-600`}
rel="noreferrer"
>
To-do Application
</a>
</p>
</footer>
);
};
export default Footer;

App.js

Here we are going to import all of our components.


import React from "react";
import NavBar from "./components/NavBar";
import Footer from "./components/Footer";
import Alert from "./components/Alert";
import AddTodo from "./components/AddTodo";
import EditTodo from "./components/EditTodo";
import Todo from "./components/Todo";
function App() {
return (
<main className="relative mx-auto h-screen w-full bg-gray-100 ">
<NavBar />
<div
className={`flex w-full flex-1 flex-col gap-6 overflow-y-auto overflow-x-hidden px-4 py-4`}
>
<Alert />
<AddTodo />
<EditTodo />
<Todo />
</div>
<Footer />
</main>
);
}
export default App;

Index.js

And finally here we are going to wrap our App.js with one of redux methods tag called a Provider


import React from "react";
import ReactDOM from "react-dom";
import { Provider } from "react-redux";
import store from "./store";
import App from "./App";
import "./style.css";
// import "./style.output.css";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);

Credits

  • This tutorial is independently created and is not official Oracle Corporation documentation.
  • The content of this tutorial has been enriched by leveraging the insights and documentation available from Oracle Corporation. We extend our thanks to Oracle for their dedication to knowledge sharing. For official Oracle resources and additional information, please refer to www.oracle.com.
Avatar

By Eric K

Experienced Software Developer with a demonstrated history of working in the computer software industry. Skilled in React.js, Vue.js, JavaScript and Node.js.

Any Query?

Ask Beejartha