Are you looking to dive into the world of web development with Vue.js? One of the best ways to learn is by building practical projects. A to-do list is a classic project, and it’s perfect for beginners because it introduces core concepts like data binding, event handling, and component composition. Furthermore, by incorporating local storage, you can learn how to persist data, making your to-do list even more functional and useful. This guide will walk you through, step-by-step, how to create an interactive to-do list app with Vue.js, complete with the ability to add, delete, and mark tasks as complete, all while saving your data in the browser’s local storage. Let’s get started!
Why Build a To-Do List?
A to-do list might seem simple, but it’s an excellent project for learning the fundamentals of any JavaScript framework, including Vue.js. Building a to-do list allows you to:
- Understand Data Binding: See how data updates in the UI when the underlying data changes.
- Master Event Handling: Learn how to respond to user interactions, such as clicking buttons or submitting forms.
- Work with Components: Break down your application into reusable and manageable components.
- Explore Local Storage: Discover how to save and retrieve data in the user’s browser, making your app persistent.
Moreover, the project is self-contained. You don’t need a backend server to store your data, which simplifies the learning process and allows you to focus on the front-end aspects of Vue.js.
Prerequisites
Before you begin, make sure you have the following:
- Basic HTML, CSS, and JavaScript knowledge: You don’t need to be an expert, but familiarity with these technologies is essential.
- Node.js and npm (or yarn) installed: You’ll need these to set up your Vue.js project. You can download Node.js from nodejs.org.
- A code editor: Visual Studio Code, Sublime Text, or any other editor of your choice.
Setting Up Your Vue.js Project
Let’s start by creating a new Vue.js project using the Vue CLI (Command Line Interface). Open your terminal or command prompt and run the following commands:
npm install -g @vue/cli
vue create todo-list-app
During the project creation process, you’ll be prompted to choose a preset. Select the default preset (babel, eslint). Navigate into your project directory:
cd todo-list-app
Now, start the development server:
npm run serve
This will start a development server, and you can access your app in your browser at http://localhost:8080/ (or a similar address). You should see the default Vue.js welcome screen.
Project Structure
Before we start writing code, let’s briefly understand the project structure created by the Vue CLI:
- src/: This directory contains your source code.
- src/components/: This directory will hold your reusable Vue components.
- src/App.vue: This is the root component of your application.
- src/main.js: This is the entry point of your application.
- public/: This directory contains static assets like your index.html file.
Building the To-Do List Components
Let’s create the components for our to-do list. We’ll need at least two components: a component to display each individual to-do item and a component to handle the form for adding new tasks.
1. Creating the TodoItem Component
Create a new file named TodoItem.vue inside the src/components directory. Add the following code:
<template>
<li :class="{ completed: todo.completed }">
<input type="checkbox" :checked="todo.completed" @change="toggleComplete">
<span>{{ 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>
.completed {
text-decoration: line-through;
}
</style>
Let’s break down this component:
- Template: Defines the structure of each to-do item. It includes a checkbox for marking the task as complete, the task text, and a delete button. The
:class="{ completed: todo.completed }"part dynamically adds the ‘completed’ class to the<li>element if thetodo.completedproperty is true. - Script: Defines the component’s logic.
- props: This section declares the properties that the component will receive from its parent. In this case, it expects a
todoobject. - methods: This section contains the methods for handling events.
- toggleComplete(): Emits a custom event called ‘toggle’ with the ID of the to-do item when the checkbox is clicked. This is how the parent component will know to update the todo item’s completion status.
- deleteTodo(): Emits a custom event called ‘delete’ with the ID of the to-do item when the delete button is clicked. This will trigger the deletion of the item from the parent’s data.
- Style: Defines the styles for the component. The
scopedattribute ensures that these styles only apply to this component.
2. Creating the TodoForm Component
Create a new file named TodoForm.vue inside the src/components directory. Add the following code:
<template>
<form @submit.prevent="addTodo">
<input type="text" v-model="newTodoText" placeholder="Add a task">
<button type="submit">Add</button>
</form>
</template>
<script>
export default {
data() {
return {
newTodoText: ''
}
},
methods: {
addTodo() {
if (this.newTodoText.trim() !== '') {
this.$emit('add', this.newTodoText.trim());
this.newTodoText = '';
}
}
}
}
</script>
Explanation:
- Template: Defines the form with an input field and an add button. The
v-modeldirective binds the input field’s value to thenewTodoTextdata property. The@submit.prevent="addTodo"prevents the default form submission behavior (page refresh) and calls theaddTodomethod. - Script: Defines the component’s logic.
- data(): Initializes the
newTodoTextdata property, which holds the text entered in the input field. - addTodo(): Emits a custom event called ‘add’ with the trimmed text from the input field when the form is submitted. It also clears the input field after adding the task.
3. Modifying the App.vue Component
Now, let’s modify the App.vue component to use these components and manage the to-do list data. Replace the contents of src/App.vue with the following code:
<template>
<div id="app">
<h1>To-Do List</h1>
<TodoForm @add="addTodo"></TodoForm>
<ul>
<TodoItem
v-for="todo in todos"
:key="todo.id"
:todo="todo"
@toggle="toggleComplete"
@delete="deleteTodo"
></TodoItem>
</ul>
</div>
</template>
<script>
import TodoItem from './components/TodoItem.vue';
import TodoForm from './components/TodoForm.vue';
export default {
components: {
TodoItem, TodoForm
},
data() {
return {
todos: []
}
},
mounted() {
this.loadTodos();
},
methods: {
addTodo(text) {
const newTodo = {
id: Date.now(),
text: text,
completed: false
};
this.todos.push(newTodo);
this.saveTodos();
},
toggleComplete(id) {
const todo = this.todos.find(todo => todo.id === id);
if (todo) {
todo.completed = !todo.completed;
this.saveTodos();
}
},
deleteTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id);
this.saveTodos();
},
saveTodos() {
localStorage.setItem('todos', JSON.stringify(this.todos));
},
loadTodos() {
const storedTodos = localStorage.getItem('todos');
if (storedTodos) {
this.todos = JSON.parse(storedTodos);
}
}
}
}
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
ul {
list-style: none;
padding: 0;
}
li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.completed {
text-decoration: line-through;
color: #999;
}
</style>
Here’s what’s happening in App.vue:
- Importing Components: Imports the
TodoItemandTodoFormcomponents. - Registering Components: Registers the imported components in the
componentsobject. - Data: Initializes the
todosarray, which will hold our to-do list items. - mounted(): The
mountedlifecycle hook is called after the component is mounted (added to the DOM). It calls theloadTodosmethod to retrieve any saved to-do items from local storage. - Methods:
- addTodo(text): This method is triggered when the
TodoFormcomponent emits the ‘add’ event. It creates a new to-do object, adds it to thetodosarray, and then callssaveTodos. - toggleComplete(id): This method is triggered when the
TodoItemcomponent emits the ‘toggle’ event. It finds the to-do item with the matching ID and toggles itscompletedproperty. It then callssaveTodos. - deleteTodo(id): This method is triggered when the
TodoItemcomponent emits the ‘delete’ event. It filters thetodosarray to remove the to-do item with the matching ID. It then callssaveTodos. - saveTodos(): This method saves the
todosarray to local storage. It usesJSON.stringify()to convert the array into a JSON string before storing it. - loadTodos(): This method loads the to-do items from local storage. It uses
JSON.parse()to convert the JSON string back into a JavaScript array. - Template: The template displays the to-do list.
- It includes the
TodoFormcomponent and listens for the ‘add’ event, calling theaddTodomethod when the event is emitted. - It uses
v-forto iterate over thetodosarray and render aTodoItemcomponent for each to-do item. - It passes the
todoobject as a prop to theTodoItemcomponent. - It listens for the ‘toggle’ and ‘delete’ events from the
TodoItemcomponent, calling thetoggleCompleteanddeleteTodomethods, respectively.
Adding Functionality
Now, let’s test our application. Start the development server (npm run serve) if it’s not already running. Open your browser and go to http://localhost:8080/. You should see:
- An input field and an “Add” button.
- An empty list.
Adding Tasks: Type a task into the input field and click the “Add” button. The task should appear in the list. If you refresh the page, the task will still be there, thanks to local storage.
Marking Tasks as Complete: Click the checkbox next to a task. The task text should be crossed out.
Deleting Tasks: Click the “Delete” button next to a task. The task should be removed from the list.
Common Mistakes and How to Fix Them
Here are some common mistakes beginners make when building a to-do list app and how to avoid them:
- Not Using v-model Correctly: Make sure you’re using
v-modelcorrectly to bind the input field’s value to a data property. This is crucial for two-way data binding. If the input field doesn’t update when you type, or the data doesn’t update when you change the input field, double-check yourv-modelbindings. - Incorrect Event Handling: Ensure that your event handlers are correctly defined and that you’re passing the correct data to them. For example, when emitting custom events from child components, make sure you’re emitting the correct data (like the to-do item’s ID).
- Forgetting to Save to Local Storage: If your to-do list doesn’t persist after a page refresh, you likely forgot to save the
todosarray to local storage whenever you add, update, or delete a task. Double-check yoursaveTodosmethod. - Incorrect Data Types in Local Storage: Local storage only stores strings. You must convert JavaScript objects and arrays to JSON strings using
JSON.stringify()before saving them to local storage, and convert them back to JavaScript objects usingJSON.parse()when retrieving them. - Not Using Scoped Styles: If your styles are affecting other parts of your application, you might have forgotten to add the
scopedattribute to your<style>tags in your components. This ensures that the styles are only applied to the component’s elements.
Key Takeaways and Next Steps
You’ve successfully built a basic, yet functional, to-do list app with Vue.js! You’ve learned about components, data binding, event handling, and local storage. Here’s a summary of the key concepts:
- Components: Vue.js apps are built from reusable components.
- Data Binding: Use
v-modelto bind input fields to data properties. - Event Handling: Use
@eventto listen for events and trigger methods. - Props: Pass data from parent to child components using props.
- Custom Events: Emit custom events from child components to communicate with parent components.
- Local Storage: Use
localStorageto persist data in the browser.
Now that you’ve completed this project, consider these next steps to further enhance your skills:
- Add Edit Functionality: Allow users to edit existing tasks.
- Implement Filtering and Sorting: Add features to filter tasks by status (e.g., “Active,” “Completed”) and sort them by date or priority.
- Use a UI Library: Explore UI component libraries like Vuetify or Element UI to create a more polished user interface.
- Integrate with a Backend: Learn how to fetch and save your to-do list data from a backend server (e.g., using Node.js and Express).
- Explore Vuex or Pinia: For larger applications, consider using a state management library like Vuex or Pinia to manage your application’s state more effectively.
As you build more projects, you’ll become more comfortable with Vue.js and web development in general. Keep practicing, experimenting, and building! The more you build, the better you’ll become.
