Introduction
Congratulations if you have persevered and followed up to this point, You are just one step away from completing your first full-stack application using PERN stack. Click on the link: PERN Stack Series 3: Building the Server to revisit the previous part of this series if you need to!
Now let's cut to the chase techies😊
Note:
Notice I have used comments at the initial stage of creating or editing some files. The reason is to avoid import errors, do not worry will clear the comments when we are done
First, we will make the following changes to the client/src/App.js
file. In a React application, App.js
is typically the main or root component file that serves as the entry point for your application. It's a convention in React projects to have an App.js
(or sometimes App.jsx
) file where you define the main structure and layout of your application.
import React, { Fragment } from "react";
import "./App.css";
//components
// import InputTodo from "./components/InputTodo";
// import ListTodo from "./components/ListTodo";
function App() {
return (
<Fragment>
{/* <div id="top-level-div">
<InputTodo />
<h3 style={{ margin: "0%", marginTop: "40px" }}>Tasks</h3>
<div table="todo-table-div">
<ListTodo />
<hr color="grey" />
<p style={{ font: "small" }} f>
© Develop Yourself
</p>
</div>
</div> */}
</Fragment>
);
}
export default App;
Next, we will make changes to the client/src/index.css
file. The index.css
file in a React project is typically a global stylesheet that applies styles to the entire application. It is often the main CSS file that gets loaded by the index.js
file, which is the entry point of the React application.
The index.css
file may include styling rules that affect the overall layout, typography, colours, and other visual aspects of the application.
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
#top-level-div {
margin: 2rem;
}
.input-field-parent {
background-color: rgb(237, 237, 237);
padding: 20px;
border-radius: 2px;
}
.add-btn {
margin-top: 5px;
width: 100%;
}
.edit-btn {
background-color: orange;
border: 1px solid black;
border-radius: 2px;
}
.input-field {
/* width: 100%; */
}
#top-level-div {
margin: 2rem;
}
#todos {
border: 1px solid rgba(162, 221, 162, 0.486);
border-radius: 2px;
border-collapse: collapse;
width: 100%;
background-color: rgba(162, 221, 162, 0.486);
}
#todos tr,
td {
border-bottom: 1px solid rgba(199, 194, 194, 0.291);
padding: 10px;
}
#todos tr:hover {
background-color: rgba(162, 227, 162, 0.486);
}
#todos .td-btns {
text-align: right;
}
hr {
background-color: rgb(235, 226, 226);
height: 1px;
border: 0;
}
In other to use more functionality from bootstrap4, we will add the CDN(Content Delivery Network) to our application by making the following changes to client/public/index.html
file. In a typical React project, the HTML file is usually named index.html
and is located in the public
directory. This file serves as the main HTML template for your React application and is the entry point for rendering React components.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"
integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favjhicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Todo Application</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js" integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+" crossorigin="anonymous"></script>
</body>
</html>
Bravo! you are doing great!
In the next section, we will be building the react components for our user interface. React components are reusable, self-contained building blocks that define how a part of a user interface (UI) should appear and behave. In React, the user interface is composed of components, which can range from small and simple UI elements (like buttons or input fields) to larger and more complex structures (like entire sections of a webpage).
This is the User Interface we are building for our application, and we will be building the following components
EditTodo
InputTodo
List Todo
Prerequisite:
We will create a folder to store our components to keep our project structure nice and arranged.
#move to client folder
cd client
#move to src folder
cd src
#create directory
mkdir components
#cd components
touch EditTodo.js InputTodo.js ListTodo.js
Building the InputTodo component:
Open the InputTodo.js
file, and copy and paste the code below in that file.
import React, { Fragment, useState } from "react";
const InputTodo = () => {
const [description, setDescription] = useState("");
const onSubmitForm = async (e) => {
e.preventDefault();
try {
const url = "http://localhost:4000/todos";
const body = { description };
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
window.location = "/";
} catch (err) {
console.error(err.message);
}
};
return (
<Fragment>
<div class="container text-center mb-3 font-weight-bold">
<h2>My Todo List</h2>
</div>
<hr />
<form onSubmit={onSubmitForm}>
<div className="input-field-parent mt-5">
<div>
<input
id="add"
type="text"
name="to-do"
className="input-field form-control"
placeholder="Enter a todo..."
value={description}
onChange={(e) => setDescription(e.target.value)}
required
/>
</div>
<div className="add-btn text-right">
<button class="btn btn-success">Add</button>
</div>
</div>
</form>
</Fragment>
);
};
export default InputTodo;
Building the EditTodo component:
Open the ListTodo.js
file we have just created in vscode, and copy and paste the code below in that file.
import React, { Fragment, useState } from "react";
const EditToDo = ({ todo }) => {
const [description, setDescription] = useState(todo.description);
//edit description funciton
const updateDescription = async (e) => {
e.preventDefault();
try {
const body = { description };
const response = await fetch(
`http://localhost:4000/todos/${todo.todo_id}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
}
);
//console.log(response )
window.location = "/";
} catch (err) {
console.error(err.message);
}
};
return (
<Fragment>
<button
style={{ color: "white" }}
type="button"
className="btn btn-warning btn-sm"
data-toggle="modal"
data-target={`#id${todo.todo_id}`}
>
Edit
</button>
<div
className="modal"
id={`id${todo.todo_id}`}
onClick={() => setDescription(todo.description)}
>
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">Edit Todo</h4>
<button
type="button"
className="close"
data-dismiss="modal"
onClick={() => setDescription(todo.description)}
>
×
</button>
</div>
<div className="modal-body">
<input
type="text"
className="form-control"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<div className="modal-footer">
<button
type="button"
className="btn btn-warning btn-sm"
data-dismiss="modal"
onClick={(e) => updateDescription(e)}
>
Edit
</button>
<button
type="button"
className="btn btn-danger btn-sm"
data-dismiss="modal"
onClick={() => setDescription(todo.description)}
>
Close
</button>
</div>
</div>
</div>
</div>
</Fragment>
);
};
export default EditToDo;
Building the ListTodo component:
Open the ListTodo.js
file in vscode and copy and paste the code below in that file.
import React, { Fragment, useEffect, useState } from "react";
import EditToDo from "./EditTodo";
const ListTodo = () => {
const [todos, setTodos] = useState([]);
//delete todo
const deleteTodo = async (id) => {
try {
const deteleTodo = await fetch(`http://localhost:4000/todos/${id}`, {
method: "DELETE",
});
setTodos(todos.filter((todo) => todo.todo_id !== id));
} catch (err) {
console.error(err.message);
}
};
//gets all todos function
const getToDos = async () => {
try {
const url = "http://localhost:4000/todos";
const response = await fetch(url);
const jsonData = await response.json(response);
//console.log(jsonData)
setTodos(jsonData);
} catch (err) {
console.error(err.message);
}
};
//useEffect
useEffect(() => {
getToDos();
}, []);
return (
<Fragment>
<hr />
<table id="todos">
<colgroup>
<col style={{ width: "97%" }} />
<col />
<col />
</colgroup>
<tbody>
{todos.map((todo) => (
<tr key={todo.todo_id}>
<td>{todo.description}</td>
<td>
<EditToDo todo={todo} />
</td>
<td>
<button
className="btn btn-danger btn-sm"
onClick={(e) => deleteTodo(todo.todo_id)}
>
{" "}
Delete{" "}
</button>
</td>
</tr>
))}
</tbody>
</table>
</Fragment>
);
};
export default ListTodo;
Now our components are finally created, we can make changes to the client/src/App.js
file, and remove the comments. Then, our to-do application will work just fine without throwing any errors.
The App.js
file will look like the below code after the comments are removed.
import React, { Fragment } from "react";
import "./App.css";
//components
import InputTodo from "./components/InputTodo";
import ListTodo from "./components/ListTodo";
function App() {
return (
<Fragment>
{ <div id="top-level-div">
<InputTodo />
<h3 style={{ margin: "0%", marginTop: "40px" }}>Tasks</h3>
<div table="todo-table-div">
<ListTodo />
<hr color="grey" />
<p style={{ font: "small" }} f>
© Develop Yourself
</p>
</div>
</div> }
</Fragment>
);
}
export default App;
If you followed the above steps appropriately, your todo application will look like the image below.
Final note
In this PERN stack project, significant milestones include App.js updates, CSS styling adjustments, Bootstrap integration through CDN, and the creation of InputTodo, EditTodo, and ListTodo components. These components facilitate seamless input handling, editing functionalities, and the display of a structured to-do list. The application boasts a well-organized architecture, ensuring a user-friendly experience. With these comprehensive changes, the to-do application is now fully functional and ready for use, showcasing effective use of React components and thoughtful styling for an enhanced user interface. Great job reaching this point in your full-stack development journey!
Don't forget, the learning never stops! 😊