Building a Simple Vue.js To-Do List App: A Beginner’s Guide

In the ever-evolving landscape of web development, JavaScript frameworks have become indispensable tools for creating dynamic and engaging user interfaces. Among these, Vue.js stands out for its approachable learning curve, versatility, and efficiency. One of the best ways to learn any framework is by building something practical, and what’s more practical than a to-do list? This article provides a comprehensive, step-by-step guide to building your own to-do list application using Vue.js, perfect for beginners and those looking to solidify their understanding of the framework.

Why Build a To-Do List App?

A to-do list app is an excellent project for several reasons:

  • It’s Beginner-Friendly: The core functionalities – adding, deleting, and marking tasks as complete – are relatively simple to implement, making it ideal for those new to Vue.js.
  • It Covers Essential Concepts: You’ll learn fundamental Vue.js concepts like components, data binding, event handling, and conditional rendering.
  • It’s Practical: Everyone can benefit from a to-do list, giving you a tangible project to showcase your skills.
  • It’s a Foundation: Once you understand the basics, you can easily expand the app with more advanced features.

Setting Up Your Development Environment

Before diving into the code, you’ll need to set up your development environment. Here’s what you’ll need:

  • Node.js and npm (Node Package Manager): Vue.js relies on Node.js and npm (or yarn) for package management. Download and install Node.js from the official website: https://nodejs.org/. npm will be installed along with Node.js.
  • A Text Editor or IDE: Choose your preferred code editor. Popular choices include Visual Studio Code, Sublime Text, Atom, or WebStorm.

Creating a Vue.js Project with Vue CLI

The Vue CLI (Command Line Interface) is the official tool for quickly scaffolding new Vue.js projects. Open your terminal or command prompt and run the following command to install the Vue CLI globally:

npm install -g @vue/cli

Once installed, create a new project using the following command. Replace “todo-app” with your desired project name:

vue create todo-app

The Vue CLI will prompt you to select a preset. Choose the “default” preset (Babel, ESLint) for simplicity. You can also manually select features to customize your project. For this project, the default setup is sufficient.

Navigate into your project directory:

cd todo-app

Start the development server:

npm run serve

This command will start a local development server, usually at http://localhost:8080/. Open this address in your web browser to see the default Vue.js application.

Project Structure Overview

Before we start coding, let’s understand the basic structure generated by the Vue CLI:

  • `src/` directory: This is where you’ll spend most of your time. It contains your components, styles, and other source files.
  • `src/components/`: This directory will hold your reusable Vue components.
  • `src/App.vue`: This is the main component of your application. It’s the root component that holds everything else.
  • `src/main.js`: This file initializes the Vue application and mounts it to the DOM.
  • `public/index.html`: This is the main HTML file, where your Vue app will be rendered.
  • `package.json`: This file lists your project’s dependencies and scripts.

Building the To-Do List Components

Now, let’s start building the components for our to-do list app. We’ll create three main components:

  • `TodoItem.vue`: Represents a single to-do item.
  • `TodoList.vue`: Displays the list of to-do items and handles adding new items.
  • `App.vue`: The main component that orchestrates everything.

1. Creating the `TodoItem.vue` Component

Create a new file named `TodoItem.vue` inside the `src/components/` directory. This component will display a single to-do item and handle the functionality to mark it as complete or delete it.

Here’s the code for `TodoItem.vue`:

<template>
 <li class="todo-item" :class="{ 'completed': todo.completed }">
 <input type="checkbox" :checked="todo.completed" @change="toggleComplete">
 <span :class="{ 'completed-text': todo.completed }">{{ todo.text }}</span>
 <button @click="deleteTodo">Delete</button>
 </li>
</template>

<script>
 export default {
 props: {
 todo: {
 type: Object,
 required: true
 }
 },
 methods: {
 toggleComplete() {
 this.$emit('toggle', this.todo.id);
 },
 deleteTodo() {
 this.$emit('delete', this.todo.id);
 }
 }
 };
</script>

<style scoped>
 .todo-item {
 display: flex;
 justify-content: space-between;
 align-items: center;
 padding: 10px;
 border-bottom: 1px solid #eee;
 }

 .completed {
 text-decoration: line-through;
 }

 .completed-text {
 text-decoration: line-through;
 }

 button {
 background-color: #f44336;
 color: white;
 border: none;
 padding: 5px 10px;
 cursor: pointer;
 border-radius: 3px;
 }
</style>

Let’s break down this code:

  • `<template>`: This section defines the HTML structure of the component.
  • `<li class=”todo-item” :class=”{ ‘completed’: todo.completed }”>`: This is the list item that represents a single to-do item. The `:class` directive dynamically adds the “completed” class if the `todo.completed` property is true.
  • `<input type=”checkbox” :checked=”todo.completed” @change=”toggleComplete”>`: This is the checkbox that allows the user to mark the task as complete. `:checked` binds the checkbox’s state to the `todo.completed` property. `@change` listens for changes to the checkbox and calls the `toggleComplete` method.
  • `<span :class=”{ ‘completed-text’: todo.completed }”>{{ todo.text }}</span>`: Displays the text of the to-do item. The `:class` directive adds the “completed-text” class if the `todo.completed` property is true, visually indicating completion.
  • `<button @click=”deleteTodo”>Delete</button>`: The delete button. `@click` listens for clicks and calls the `deleteTodo` method.
  • `<script>`: This section contains the JavaScript logic for the component.
  • `props: { todo: { type: Object, required: true } }`: This defines a prop named `todo`. Props are how you pass data into a component. The `todo` prop is expected to be an object and is required.
  • `methods: { toggleComplete() { … }, deleteTodo() { … } }`: Defines the methods that handle the checkbox’s `change` event and the delete button’s `click` event. Both methods emit custom events that will be handled by the parent component (TodoList).
  • `<style scoped>`: This section contains the CSS styles for the component. The `scoped` attribute ensures that these styles only apply to this component.

2. Creating the `TodoList.vue` Component

Create a new file named `TodoList.vue` inside the `src/components/` directory. This component will manage the list of to-do items, handle adding new items, and interact with the `TodoItem` component.

Here’s the code for `TodoList.vue`:

<template>
 <div class="todo-list-container">
 <h2>To-Do List</h2>
 <input type="text" v-model="newTodoText" @keyup.enter="addTodo" placeholder="Add a new task">
 <ul>
 <todo-item
 v-for="todo in todos"
 :key="todo.id"
 :todo="todo"
 @toggle="toggleTodo"
 @delete="deleteTodo"
 >
 </todo-item
 >
 </ul>
 </div>
</template>

<script>
 import TodoItem from './TodoItem.vue';

 export default {
 components: {
 TodoItem
 },
 data() {
 return {
 todos: [
 {
 id: 1,
 text: 'Learn Vue.js',
 completed: false
 },
 {
 id: 2,
 text: 'Build a To-Do List',
 completed: true
 }
 ],
 newTodoText: ''
 };
 },
 methods: {
 addTodo() {
 if (this.newTodoText.trim() !== '') {
 const newTodo = {
 id: Math.max(...this.todos.map(todo => todo.id)) + 1, // Generate a unique ID
 text: this.newTodoText.trim(),
 completed: false
 };
 this.todos.push(newTodo);
 this.newTodoText = ''; // Clear the input field
 }
 },
 toggleTodo(id) {
 const todo = this.todos.find(todo => todo.id === id);
 if (todo) {
 todo.completed = !todo.completed;
 }
 },
 deleteTodo(id) {
 this.todos = this.todos.filter(todo => todo.id !== id);
 }
 }
 };
</script>

<style scoped>
 .todo-list-container {
 width: 80%;
 margin: 20px auto;
 padding: 20px;
 border: 1px solid #ccc;
 border-radius: 5px;
 }

 input[type="text"] {
 width: 100%;
 padding: 10px;
 margin-bottom: 10px;
 border: 1px solid #ccc;
 border-radius: 3px;
 }

 ul {
 list-style: none;
 padding: 0;
 }
</style>

Let’s break down this code:

  • `<template>`: This section defines the HTML structure of the component.
  • `<h2>To-Do List</h2>`: A heading for the to-do list.
  • `<input type=”text” v-model=”newTodoText” @keyup.enter=”addTodo” placeholder=”Add a new task”>`: An input field for adding new to-do items. `v-model` is a Vue directive that creates two-way data binding. It binds the input field’s value to the `newTodoText` data property. `@keyup.enter` listens for the “Enter” key press and calls the `addTodo` method.
  • `<ul>`: An unordered list to display the to-do items.
  • `<todo-item … ></todo-item>`: This is where we use the `TodoItem` component.
  • `v-for=”todo in todos” :key=”todo.id”`: The `v-for` directive iterates over the `todos` array and renders a `TodoItem` component for each item. `:key` provides a unique identifier for each item, which is important for Vue’s update process.
  • `:todo=”todo”`: This passes the current `todo` object as a prop to the `TodoItem` component.
  • `@toggle=”toggleTodo” @delete=”deleteTodo”`: These listen for custom events emitted by the `TodoItem` component. When the `TodoItem` component emits a ‘toggle’ event, the `toggleTodo` method in `TodoList.vue` is called. Similarly, when the `TodoItem` component emits a ‘delete’ event, the `deleteTodo` method in `TodoList.vue` is called.
  • `<script>`: This section contains the JavaScript logic for the component.
  • `import TodoItem from ‘./TodoItem.vue’;`: Imports the `TodoItem` component.
  • `components: { TodoItem }`: Registers the `TodoItem` component so it can be used in the template.
  • `data() { … }`: This function returns an object containing the component’s data.
  • `todos: [ … ]`: An array to store the to-do items. It’s initialized with some sample data.
  • `newTodoText: ”`: A string to store the text entered in the input field.
  • `methods: { … }`: Defines the methods for the component.
  • `addTodo() { … }`: Adds a new to-do item to the `todos` array when the user presses Enter or clicks a button.
  • `toggleTodo(id) { … }`: Toggles the completion status of a to-do item.
  • `deleteTodo(id) { … }`: Removes a to-do item from the list.
  • `<style scoped>`: This section contains the CSS styles for the component.

3. Integrating the `TodoList.vue` Component into `App.vue`

Finally, open `src/App.vue` and integrate the `TodoList` component. This is the main component that will render the to-do list.

Here’s the code for `App.vue`:

<template>
 <div id="app">
 <todo-list></todo-list>
 </div>
</template>

<script>
 import TodoList from './components/TodoList.vue';

 export default {
 components: {
 TodoList
 }
 };
</script>

<style>
 #app {
 font-family: Avenir, Helvetica, Arial, sans-serif;
 -webkit-font-smoothing: antialiased;
 -moz-osx-font-smoothing: grayscale;
 text-align: center;
 color: #2c3e50;
 margin-top: 60px;
 }
</style>

Let’s break down this code:

  • `<template>`: This section defines the HTML structure of the component.
  • `<div id=”app”>`: The main container for the application.
  • `<todo-list></todo-list>`: This is where we use the `TodoList` component.
  • `<script>`: This section contains the JavaScript logic for the component.
  • `import TodoList from ‘./components/TodoList.vue’;`: Imports the `TodoList` component.
  • `components: { TodoList }`: Registers the `TodoList` component so it can be used in the template.
  • `<style>`: This section contains the CSS styles for the component. These styles are not `scoped` because they apply to the entire app.

Running and Testing the Application

Now, save all the files and go back to your browser. You should see your to-do list application running! You should be able to:

  • Add new to-do items by typing in the input field and pressing Enter.
  • Mark items as complete by clicking the checkboxes.
  • Delete items by clicking the delete buttons.

Common Mistakes and How to Fix Them

As a beginner, you might encounter some common mistakes. Here’s a list of potential issues and how to resolve them:

  • Incorrect File Paths: Double-check your file paths in the `import` statements. Typos can prevent components from loading.
  • Missing Component Registration: Make sure you’ve registered your components in the `components` option of your Vue components.
  • Data Binding Issues: Ensure you are using `v-model` for two-way data binding in input fields and that you are correctly passing data as props.
  • Event Handling Problems: Verify that your event listeners (`@click`, `@change`, etc.) are correctly attached to the elements and that the corresponding methods are defined in your component’s `methods` option.
  • Incorrect CSS Selectors: If your styles aren’t being applied, check your CSS selectors and make sure they are specific enough to target the elements you want to style. Remember the `scoped` attribute!
  • ID Generation: When generating unique IDs, make sure they are actually unique. Using the `Math.max(…this.todos.map(todo => todo.id)) + 1` method is one way to achieve this, but consider other strategies for more complex applications, like using UUIDs.

Expanding the To-Do List App (Optional Enhancements)

Once you have the basic to-do list working, you can expand it with more advanced features:

  • Local Storage: Save the to-do items to the browser’s local storage so they persist even when the user closes the browser.
  • Filtering and Sorting: Add options to filter the to-do items (e.g., show only incomplete items) and sort them by date or priority.
  • Due Dates and Priorities: Allow users to set due dates and priorities for their tasks.
  • Edit Functionality: Enable users to edit the text of existing to-do items.
  • User Authentication: For a more complex application, implement user accounts to save and manage to-do lists for multiple users.

Key Takeaways

  • You’ve successfully built a basic to-do list app using Vue.js.
  • You’ve learned how to create and use components, handle data, bind data to the UI, and respond to user events.
  • You’ve gained practical experience with essential Vue.js directives like `v-model`, `v-for`, and `:class`.
  • You’ve learned how to structure a Vue.js project using the Vue CLI.

Building this simple to-do list app is a great starting point for your Vue.js journey. It provides a solid foundation for understanding the core concepts of the framework. From here, you can explore more advanced features, build more complex applications, and continue to expand your knowledge of web development.