TO-DO APP USING REACT, VITE, AND BOOTSTRAP - PART 2: CREATE CUSTOM COMPONENTS
In React Mar 31, 2024
Updated on June 8, 2024
In this post, we will continue to develop our application by creating custom components. Also, we'll persist data using localStorage.
ALL PARTS:
- To-Do App Using React, Vite, And Bootstrap: Set Up the Application
- To-Do App Using React, Vite, And Bootstrap: Create Custom Components
- To-Do App Using React, Vite, And Bootstrap: Create Tasks
- To-Do App Using React, Vite, And Bootstrap: Edit Tasks
- To-Do App Using React, Vite, And Bootstrap: Delete and Update Tasks Status
Create a Custom Component
In this step, we'll extract the task list rendering logic from the App
component and encapsulate it
within its custom component called Tasks
.
Let's begin by creating a new file named Tasks.jsx
in the src
folder. Inside this file,
we'll define the Tasks
component:
import { v4 as uuidv4 } from "uuid";
const Tasks = () => {
const tasks = [
{ id: uuidv4(), title: "Feed the cats", completed: false },
{ id: uuidv4(), title: "Exercise", completed: true },
{ id: uuidv4(), title: "Go Shopping", completed: false },
];
return (
<>
<h1>Tasks</h1>
<ul className="mt-5 list-unstyled">
{tasks.map((task) => (
<li key={task.id}>
<span>{task.title}</span>
</li>
))}
</ul>
</>
);
};
export default Tasks;
We've put the heading and the list element inside a React fragment. React fragments let you group elements without adding a new element to the DOM.
Next, let's import the Tasks
component in the App
component. After the other import
statements, add this:
import Tasks from "./Tasks.jsx";
We can delete the tasks
variable in this component. Also we can delete the uuid
import
statement. And we can delete the content within the container div in the App
component
and put the Tasks
component instead:
<div className="container">
<Tasks />
</div>
Introduction to Hooks: The state Hook
React Hooks are functions that allow functional components to "hook into" React
features like state
and lifecycle methods. They provide a way to use state
and other
React features without writing a class component.
Understanding State:
In React, the state represents the data that a component manages internally. It allows components to store and manage information that can change over time, such as user input, fetched data, or UI state.
Introducing the useState Hook:
The useState
hook is one of the most commonly used React hooks
. It allows
functional components to add stateful behavior by declaring state variables. Here's how it works:
-
Syntax:
const [state, setState] = useState(initialState)
; -
Parameters:
-
initialState
: The initial value of the state variable.
-
-
Return Value:
-
state
: The current value of the state variable. -
setState
: A function that allows updating the state variable.
-
We have a list of tasks that we've hard-coded into our application. However, we want it to be updated when we add, edit, or delete tasks.
That's why we'll use the useState
hook. First, let's import it into the Tasks
component:
import { useState } from "react";
You can delete this import from the App
component.
Now, let's remove the regular variable tasks
, and add a state variable:
const [tasks, setTasks] = useState([
{ id: uuidv4(), title: "Feed the cats", completed: false },
{ id: uuidv4(), title: "Exercise", completed: true },
{ id: uuidv4(), title: "Go Shopping", completed: false },
]);
PERSIST DATA
To persist data during a session in a React application, we can use a browser storage mechanism such
as localStorage
. localStorage
is a browser API
that allows us to store
key-value pairs locally in the user's web browser. The data stored in localStorage
persists even after
the browser is closed and reopened, making it suitable for storing session data, user preferences, or any other data
that needs to persist across page reloads within the same browser.
You can retrieve data from localStorage
using the getItem
method. It takes one argument: the
key of the item you want to retrieve. We can use this method to retrieve data into our application.
Initializing State with localStorage Data
In the Tasks
component, we're initializing the tasks state using a callback function provided to
useState
.
This callback function retrieves the tasks data from localStorage
using getItem("tasks")
.
Let's modify the tasks variable like this:
const [tasks, setTasks] = useState(() => {
const savedTasks = localStorage.getItem("tasks");
return savedTasks ? JSON.parse(savedTasks) : [];
});
There are no tasks for no, so nothing is shown.
Updating localStorage When tasks State Changes
We can use the useEffect
hook to listen for changes to the task's state.
Let's first import it into the Tasks
component:
import { useState, useEffect } from "react";
Then we add this before the return
statement:
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(tasks));
}, [tasks]);
We're using the useEffect
hook to listen for changes to the tasks state.
Whenever the tasks state changes, we're updating the data in localStorage
using
setItem("tasks", JSON.stringify(tasks))
.
We're converting the tasks array to a JSON
string using JSON.stringify
because
localStorage
can only store strings.
With this setup, the tasks data is saved to localStorage
whenever it changes, and it's retrieved from
localStorage
when the App component mounts. This ensures that the tasks data persists across page reloads
within the same browser session, providing a seamless user experience.
We can delete the uuid
import statement now, because we don't need it anymore.
Create a Task Component
One of the fundamental principles of React development is the concept of modularity – breaking down the user interface into smaller, reusable components. This approach not only enhances code organization but also promotes code reusability and maintainability.
To adhere to this principle, we'll add a separate Task
component. This component is responsible for
rendering individual task items within our application. By isolating the rendering logic for a single task into its
own component, we achieve a clear separation of concerns.
Let's create a file called Task.jsx
in the src
folder.
Inside this component, we will return the list item currently in the Tasks
component. Let's edit it as
follows:
const Task = ({ task }) => {
return (
<li className="d-flex align-items-center border-bottom border-dark pb-2 pt-2">
<span>{task.title}</span>
</li>
);
};
export default Task;
We've added some Bootstrap classes to this component. Now, let's import it into the
Tasks
component:
import Task from "./Task.jsx";
And we'll use it inside the tasks list. We'll edit it like this:
<ul className="mt-5 list-unstyled">
{tasks.map((task) => (
<Task key={task.id} task={task} />
))}
</ul>
To integrate the Task
component into our Tasks
component, we pass each task as a prop. This
approach enables the Task
component to dynamically render based on the properties of the task it
receives. To ensure efficient management and updates of the list, we use the key
prop to uniquely
identify each task item in the list.