Build a Next.js Interactive Web-Based Simple Task Management App

Written by

in

In today’s fast-paced world, staying organized is key to productivity. Whether you’re juggling work projects, personal errands, or creative endeavors, a well-structured task management system can make all the difference. While complex project management tools exist, sometimes all you need is a simple, intuitive way to track your tasks. In this tutorial, we’ll dive into building a basic, yet functional, task management application using Next.js. This project is perfect for beginners and intermediate developers looking to hone their skills in Next.js, React, and state management.

Why Build a Task Management App?

Creating a task management app provides several benefits:

  • Practical Application: You’ll learn how to build a real-world application, applying your coding skills to solve a common problem.
  • Skill Enhancement: You’ll gain hands-on experience with core web development concepts like state management, event handling, and component composition.
  • Portfolio Piece: A functional task management app is a great addition to your portfolio, showcasing your ability to create interactive web applications.
  • Customization: You can tailor the app to your specific needs, adding features and functionalities as you progress.

This tutorial will guide you through the entire process, from setting up your Next.js project to implementing key features like adding, deleting, and marking tasks as complete. We’ll keep the explanations simple and provide clear code examples, making it easy to follow along.

Prerequisites

Before we begin, make sure you have the following installed on your system:

  • Node.js and npm: You’ll need Node.js and npm (Node Package Manager) installed to manage project dependencies.
  • A Code Editor: A code editor like Visual Studio Code (VS Code), Sublime Text, or Atom is recommended.
  • Basic Understanding of JavaScript and React: Familiarity with JavaScript and the fundamentals of React will be helpful.

Setting Up Your Next.js Project

Let’s start by creating a new Next.js project. Open your terminal and run the following command:

npx create-next-app task-management-app

This command will set up a new Next.js project named “task-management-app”. Navigate into the project directory:

cd task-management-app

Now, let’s install any necessary dependencies. For this project, we’ll primarily be using React for the UI and potentially a state management library like `useState` (which is part of React) for managing our tasks. We won’t need any external libraries for this basic version, but we may add them in the future. However, it’s always good practice to initialize a git repository:

git init

Project Structure

Next.js projects have a specific file structure. Here’s a brief overview of the key directories and files we’ll be working with:

  • `pages/`: This directory contains your application’s pages. Each file in this directory represents a route in your application. For instance, `pages/index.js` will be the homepage.
  • `components/`: We’ll create this directory to store reusable React components.
  • `styles/`: This directory will hold your CSS or styling files.
  • `public/`: This directory is for static assets like images, fonts, etc.
  • `package.json`: This file lists your project’s dependencies and scripts.

Building the Task List Component

First, let’s create a `TaskItem` component to represent each individual task. Create a new file inside the `components/` directory called `TaskItem.js`:

// components/TaskItem.js
import React from 'react';

function TaskItem({ task, onDelete, onToggleComplete }) {
  return (
    <div className="task-item">
      <input
        type="checkbox"
        checked={task.completed}
        onChange={() => onToggleComplete(task.id)}
      />
      <span className={task.completed ? 'completed' : ''}>{task.text}</span>
      <button onClick={() => onDelete(task.id)}>Delete</button>
    </div>
  );
}

export default TaskItem;

This component takes three props: `task` (an object representing the task), `onDelete` (a function to delete the task), and `onToggleComplete` (a function to toggle the task’s completion status). It renders a checkbox for marking the task as complete, the task text, and a delete button.

Now, let’s create the main `TaskList` component. Create a new file inside the `components/` directory called `TaskList.js`:

// components/TaskList.js
import React from 'react';
import TaskItem from './TaskItem';

function TaskList({ tasks, onDeleteTask, onToggleTaskComplete }) {
  return (
    <div className="task-list">
      {tasks.map((task) => (
        <TaskItem
          key={task.id}
          task={task}
          onDelete={onDeleteTask}
          onToggleComplete={onToggleTaskComplete}
        />
      ))}
    </div>
  );
}

export default TaskList;

The `TaskList` component receives an array of `tasks`, and two functions: `onDeleteTask` and `onToggleTaskComplete`. It iterates over the `tasks` array and renders a `TaskItem` component for each task, passing the task data, the delete function, and the toggle complete function as props. The `key` prop is crucial for React to efficiently update the list.

Creating the Add Task Form

Now, let’s create a component for adding new tasks. Create a file named `AddTaskForm.js` in the `components/` directory:

// components/AddTaskForm.js
import React, { useState } from 'react';

function AddTaskForm({ onAddTask }) {
  const [text, setText] = useState('');

  const handleSubmit = (e) => {
    e.preventDefault();
    if (text.trim() !== '') {
      onAddTask(text.trim());
      setText('');
    }
  };

  return (
    <form onSubmit={handleSubmit} className="add-task-form">
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Add a new task"
      />
      <button type="submit">Add</button>
    </form>
  );
}

export default AddTaskForm;

This component uses the `useState` hook to manage the input field’s value. The `handleSubmit` function is called when the form is submitted. It prevents the default form submission behavior, calls the `onAddTask` prop function (which will be defined in our main page), and clears the input field. The `onAddTask` prop is a function that will be used to add the new task to our main task list.

Building the Main Page (index.js)

Now, let’s put everything together in our main page, `pages/index.js`:

// pages/index.js
import React, { useState } from 'react';
import AddTaskForm from '../components/AddTaskForm';
import TaskList from '../components/TaskList';

function Home() {
  const [tasks, setTasks] = useState([]);

  const addTask = (text) => {
    const newTask = {
      id: Date.now(), // Simple unique ID
      text: text,
      completed: false,
    };
    setTasks([...tasks, newTask]);
  };

  const deleteTask = (id) => {
    setTasks(tasks.filter((task) => task.id !== id));
  };

  const toggleTaskComplete = (id) => {
    setTasks(
      tasks.map((task) =>
        task.id === id ? { ...task, completed: !task.completed } : task
      )
    );
  };

  return (
    <div className="container">
      <h1>Task Management App</h1>
      <AddTaskForm onAddTask={addTask} />
      <TaskList
        tasks={tasks}
        onDeleteTask={deleteTask}
        onToggleTaskComplete={toggleTaskComplete}
      />
    </div>
  );
}

export default Home;

In this component, we use the `useState` hook to manage the `tasks` state, which is an array of task objects. We define three functions: `addTask`, `deleteTask`, and `toggleTaskComplete` to handle adding, deleting, and toggling the completion status of tasks, respectively. The `addTask` function creates a new task object with a unique ID (using `Date.now()`), the provided text, and a `completed` status of `false`. It then updates the `tasks` state by adding the new task to the existing tasks array using the spread operator (`…`). The `deleteTask` function filters the `tasks` array, removing the task with the specified ID. The `toggleTaskComplete` function maps over the `tasks` array and toggles the `completed` status of the task with the matching ID. Finally, we render the `AddTaskForm` and `TaskList` components, passing the necessary props to them.

Styling the Application

Let’s add some basic styling to make our app look presentable. We’ll use CSS modules for this, which is a recommended approach in Next.js. Create a file named `styles/Home.module.css` (or any name you prefer, but keep it consistent) and add the following CSS:

/* styles/Home.module.css */
.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
  font-family: sans-serif;
}

h1 {
  text-align: center;
}

.add-task-form {
  display: flex;
  margin-bottom: 20px;
}

.add-task-form input {
  flex-grow: 1;
  padding: 10px;
  margin-right: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.add-task-form button {
  padding: 10px 20px;
  background-color: #0070f3;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.task-list {
  list-style: none;
  padding: 0;
}

.task-item {
  display: flex;
  align-items: center;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.task-item input[type="checkbox"] {
  margin-right: 10px;
}

.task-item span {
  flex-grow: 1;
}

.task-item button {
  background-color: #f44336;
  color: white;
  border: none;
  padding: 5px 10px;
  border-radius: 4px;
  cursor: pointer;
}

.completed {
  text-decoration: line-through;
  color: #888;
}

Now, import this CSS module into your `pages/index.js` file:

// pages/index.js
import React, { useState } from 'react';
import AddTaskForm from '../components/AddTaskForm';
import TaskList from '../components/TaskList';
import styles from '../styles/Home.module.css'; // Import the CSS module

function Home() {
  // ... (rest of the code)

  return (
    <div className={styles.container}>  <!-- Use the CSS module styles -->
      <h1>Task Management App</h1>
      <AddTaskForm onAddTask={addTask} />
      <TaskList
        tasks={tasks}
        onDeleteTask={deleteTask}
        onToggleTaskComplete={toggleTaskComplete}
      />
    </div>
  );
}

export default Home;

To use the styles, you access them through the `styles` object (e.g., `styles.container`). This ensures that your CSS is scoped to the component and avoids potential naming conflicts.

Running Your Application

To run your Next.js application, open your terminal and navigate to your project directory. Then, run the following command:

npm run dev

This will start the development server. Open your web browser and go to `http://localhost:3000` to see your application in action. You should be able to add tasks, mark them as complete, and delete them.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid or fix them:

  • Incorrect State Updates: When updating state in React, you must always create a new state object or array. Directly modifying the existing state object will not trigger a re-render. Use the spread operator (`…`) to create new arrays and objects.
  • Missing `key` Prop: When rendering lists of components, always provide a unique `key` prop to each element. This helps React efficiently update the list. The `key` prop should be unique for each item.
  • Incorrect Event Handling: Make sure you are passing the correct event handlers to your components. In React, event handlers are typically functions that are passed as props.
  • CSS Conflicts: Without CSS modules, your CSS rules might unintentionally affect other parts of your application. Using CSS modules scopes your styles to the component, preventing conflicts.
  • Forgetting to Import Components: Always remember to import the components you are using in your files.

Enhancements and Next Steps

This is a basic task management app. Here are some ideas for further enhancements:

  • Local Storage: Persist tasks in local storage so they are not lost when the page is refreshed.
  • Task Editing: Add functionality to edit existing tasks.
  • Task Filtering: Implement filtering options (e.g., show all tasks, only completed tasks, only incomplete tasks).
  • Prioritization: Add a priority level to each task.
  • Due Dates: Add due dates to tasks.
  • Backend Integration: Connect the app to a backend database (e.g., Firebase, Supabase, or a custom API) to store tasks persistently.
  • User Authentication: Implement user authentication to allow multiple users to manage their tasks.
  • Styling with a CSS framework (Tailwind CSS, Bootstrap): Integrate a CSS framework for faster styling and a more polished look.

Key Takeaways

In this tutorial, you’ve learned how to build a simple task management app using Next.js. You’ve gained practical experience with:

  • Creating and structuring a Next.js project.
  • Building reusable React components.
  • Managing state with the `useState` hook.
  • Handling events and user input.
  • Styling your application with CSS modules.

Frequently Asked Questions (FAQ)

Q: How do I deploy this application?

A: You can deploy your Next.js application to various platforms, such as Vercel (which is recommended as it’s built by the creators of Next.js), Netlify, or AWS. The deployment process typically involves pushing your code to a Git repository and configuring the deployment platform to build and deploy your application.

Q: How can I add more features to the app?

A: The “Enhancements and Next Steps” section above provides several ideas for adding more features. You can also explore using third-party libraries for things like date pickers, rich text editors, and more advanced state management solutions like Redux or Zustand.

Q: Why am I getting an error related to “module not found”?

A: This usually means you haven’t installed a required dependency. Double-check your `package.json` file and make sure all dependencies are installed by running `npm install` in your project directory.

Q: How can I improve the performance of my app?

A: Next.js is designed for performance. However, you can further optimize your app by:

  • Using image optimization techniques.
  • Code splitting to load only the necessary code for each page.
  • Caching data.
  • Minifying your CSS and JavaScript.

This project serves as a solid foundation for exploring the world of web development. By understanding the concepts covered in this tutorial, you can build more complex and sophisticated web applications.