Hello! Welcome back to the final capstone project.
In our last lesson, we successfully set up the navigational structure of our Task Manager using React Router. We have distinct pages for the dashboard, projects, and profile, all neatly wrapped in a shared layout with a sidebar. Our DashboardPage is currently a blank canvas, waiting to be brought to life.
Today, we will focus on that canvas. Our goal is to create the responsive and interactive user interface for creating, viewing,updating, and deleting tasks. We will build out the DashboardPage to be a fully functional, self-contained task manager.
To do this, we'll work entirely on the frontend for now, managing our tasks in React's local state. This is a common and highly effective development practice: build and perfect the UI in isolation before connecting it to a live backend.
By the end of this lesson, you will have a UI that looks and feels like a complete application, ready for us to plug into our backend API in the next step.

Part 1: Structuring the UI with Components and State
First, we need a plan. A complex UI like a task manager is best built by breaking it down into smaller, reusable components. For our DashboardPage, a good structure would be:
DashboardPage.jsx: The main container component. It will own the state (the list of tasks) and the logic (functions to add, update, delete tasks).- A form for adding tasks: This will be part of the
DashboardPage's JSX. It will include a text input and a submit button. - A task item component: A separate component to represent a single task in the list. It will display the task's text and have its own controls, like a "complete" checkbox and a "delete" button.
The core of our application will be the list of tasks. We'll manage this list using the useState hook.
// Inside DashboardPage.jsx
const [tasks, setTasks] = useState([]);
This tasks array will hold task objects. A typical task object might look like this:{ id: 1, text: 'Build the task UI', completed: false }
Part 2: A Step-by-Step Video Guide
Building a to-do list is a classic React exercise, and watching an expert build one is an excellent way to learn the patterns. The following video provides a complete walkthrough that aligns perfectly with our goal for today. It uses React, Tailwind CSS for styling, and covers all the CRUD (Create, Read, Update, Delete) operations we need.
We will use this video as our primary guide for the implementation. I'll break it down into logical steps. As you follow along, focus on understanding the why behind each piece of code.
How To Make To-Do List App Using React JS And Tailwind CSS | Create Task App In React JS
The 'How To Make To-Do List App' tutorial by GreatStack is a fantastic, practical guide. We will use it to build our task management UI. The video author builds it as a standalone app, but you'll be implementing this logic inside your existing DashboardPage.jsx.
You can watch this video from the start up to 01:02:06. I've broken down the key segments below so you can focus on one piece of functionality at a time.
Follow the sections of the video to implement the functionality. I've provided explanations and code snippets to clarify the key concepts.
Step 2.1: Building the UI Structure and Rendering Tasks (Read)
First, let's get the basic UI on the screen and render a static list. This corresponds to the Read part of CRUD.
Focus in the video: 02:41 to 32:53
This part of the video covers:
- Setting up the main component structure (
Todo.jsxandTodoItems.jsxin the video, which you can adapt into yourDashboardPage.jsxand a newTaskItem.jsxcomponent). - Styling the components with Tailwind CSS to create the input box, button, and task list area.
- Creating the basic JSX for an individual task item.
The key pattern for rendering the list is using the .map() method on your state array:
// Inside the return statement of DashboardPage.jsx
<div className="task-list">
{tasks.map((task) => (
<TaskItem
key={task.id}
task={task}
/>
))}
</div>
Step 2.2: Adding New Tasks (Create)
Now, let's make the input form functional.
Focus in the video: 34:43 to 46:04
The logic for adding a task is as follows:
- Capture the user's input from the text field. The video uses
useRef, which is a valid approach. Another common way is to use a separate piece of state:const [newTaskText, setNewTaskText] = useState('');. - When the "Add" button is clicked, create a new task object. It's crucial to give it a unique
id. The video usesDate.now(), which is simple and effective for client-side state. - Update the
tasksstate array by creating a new array that includes all the old tasks plus the new one. This is a fundamental concept in React: never mutate state directly.
// A function inside DashboardPage.jsx
const addTask = (text) => {
if (!text.trim()) return; // Don't add empty tasks
const newTask = {
id: Date.now(), // Simple unique ID
text: text,
completed: false,
};
setTasks([...tasks, newTask]); // Use the spread operator to create a new array
};
Test your understanding!
Why is it important to use setTasks([...tasks, newTask]) instead of tasks.push(newTask) followed by setTasks(tasks)?
Show answer
React determines whether to re-render a component by checking if its state or props have changed. When you use array methods that mutate the array in place, like .push(), the reference to the tasks array in memory remains the same. React performs a shallow comparison and concludes that nothing has changed, so it doesn't trigger a re-render.
By using the spread operator [...tasks, newTask], you are creating a brand new array with a new reference in memory. When you pass this new array to setTasks, React sees that the state has changed and correctly re-renders the component to display the new task.
Step 2.3: Deleting Tasks (Delete)
Next, let's implement the delete functionality.
Focus in the video: 46:56 to 51:49
The pattern here is called "lifting state up". The state lives in DashboardPage, but the delete action is triggered from a button in TaskItem.
- Create a
deleteTaskfunction inDashboardPagethat takes anidas an argument. - This function will use the
.filter()method to create a new array that excludes the task with the matchingid. - Pass the
deleteTaskfunction down to eachTaskItemas a prop. - In
TaskItem, call the function from theonClickhandler of the delete button, passing its owntask.id.
// A function inside DashboardPage.jsx
const deleteTask = (id) => {
setTasks(tasks.filter(task => task.id !== id));
};
// In the .map() method in DashboardPage.jsx
<TaskItem
key={task.id}
task={task}
onDelete={deleteTask} // Pass the function as a prop
/>
// Inside TaskItem.jsx
<button onClick={() => onDelete(task.id)}>Delete</button>
Step 2.4: Updating Tasks (Update)
Finally, let's allow users to mark tasks as complete.
Focus in the video: 51:49 to 01:02:06
This follows the exact same pattern as deleting.
- Create a
toggleCompletefunction inDashboardPagethat takes anid. - This function uses
.map()to create a new array. For each task, it checks if theidmatches. If it does, it returns a new task object with thecompletedproperty flipped. If not, it returns the original task. - Pass this
toggleCompletefunction down toTaskItemas a prop. - In
TaskItem, use this function in anonClickhandler (e.g., on the checkbox or the task text).
// A function inside DashboardPage.jsx
const toggleComplete = (id) => {
setTasks(
tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
)
);
};
This function is a great example of immutable updates. It doesn't change any of the original objects; it builds a completely new array with one new, updated object in it.
Part 3: Consolidating with Code Examples
The video is a great visual guide. The article below provides concise code snippets for these same CRUD functions, which can be very helpful for reference. This article implements them as async functions preparing for API calls, which is exactly what we will do in the next lesson. For today, you can ignore the async/await and fetch parts and focus on the state manipulation logic (setTodos).
React sample project: Task Manager
The 'React sample project: Task Manager' article from Beeceptor has excellent, production-ready examples of the handler functions we need. Use it as a reference for the logic.
Review 'Step 6: Add Task Creation', 'Step 7: Implement Task Updates', and 'Step 8: Add Task Deletion'. Pay attention to the setTodos(...) calls to see how the state is updated for each action. Also, look at 'Step 10: Build the Complete UI' for a complete JSX example that puts it all together.
Conclusion
Congratulations! You have now built a responsive and fully interactive task management UI within your DashboardPage. While it currently only stores data in local state (which is lost on refresh unless you implemented the optional localStorage step from the video), you have all the necessary components, state, and logic in place.
Key Takeaways:
- You structured a complex UI by breaking it down into smaller, manageable components.
- You managed the application's core data (the list of tasks) using the
useStatehook. - You implemented all four CRUD operations on the frontend using immutable state update patterns (
map,filter, spread syntax). - You practiced the "lifting state up" pattern by passing handler functions as props from a parent component (
DashboardPage) to a child (TaskItem).
Preview of the Next Lesson:
Our UI is functional but isolated. In the next lesson, we will bridge the gap between our frontend and backend. We will replace our local state management with live data from our server by wiring up our addTask, deleteTask, and toggleComplete functions to the REST API endpoints we built earlier. This will make our Task Manager a true full-stack application.