After completing my first project—a weather app—I decided to take on a second project to further explore Vue.js: a to-do list app. This project allowed me to dive deeper into Vue’s reactivity system, local storage, and form handling. Here’s a breakdown of what I’ve learned from building it.
1. Form Handling: Preventing Page Refresh with @submit.prevent
One of the first things I realized was how powerful Vue’s form handling is, especially the @submit.prevent
directive. By using this, I was able to prevent the default behavior of form submissions, which typically results in a page refresh. In my app, I used @submit.prevent="addTask"
to ensure that when users add a task, the page doesn’t reload.
<form @submit.prevent="addTask" class="d-flex justify-content-center">
<div class="form-group">
<input type="text" class="form-control" placeholder="Add task..." v-model="task">
</div>
<span class="ml-2"><button type="submit" class="btn btn-success">Add</button></span>
</form>
However, I learned that when you prevent form submission in Vue, you need to ensure the input field has a v-model
bound to a reactive data property. Otherwise, the form may still behave as if it’s refreshing since the input value isn't being tracked or updated in Vue’s reactive system.
2. Reactivity: Managing Tasks with ref
Just like in my weather app, I used Vue’s ref
function to manage the state of my tasks and the current input. This allowed me to store tasks in an array and dynamically update the UI when new tasks were added or deleted:
const task = ref('');
const tasks = ref([]);
const addTask = () => {
if (task.value.trim() === '') return; // prevent adding empty tasks
let taskObject = { id: Date.now(), name: task.value, checked: false };
tasks.value.push(taskObject);
updateLocalStorage();
task.value = '';
};
This reactivity is key to keeping the app dynamic. Any changes to the tasks
array automatically trigger re-renders in the UI, without any need for manual DOM manipulation. This is where Vue really shines.
3. Local Storage: Making the App Persistent
One of the key features I wanted to implement in my to-do list app was persistence. I didn’t want users to lose their tasks when they refreshed the page, so I decided to store the tasks in localStorage
. I learned that Vue’s onMounted
lifecycle hook is perfect for restoring data when the app loads:
onMounted(() => {
const storedTasks = localStorage.getItem('tasks');
tasks.value = storedTasks ? JSON.parse(storedTasks) : [];
});
This way, when the user opens the app, it checks localStorage
for any previously saved tasks and loads them into the app’s state. Whenever a task is added, modified, or deleted, the updateLocalStorage
function is called to save the latest state of the tasks:
const updateLocalStorage = () => {
localStorage.setItem('tasks', JSON.stringify(tasks.value));
};
This was a valuable lesson in making apps more user-friendly by maintaining state across sessions.
4. Task Management: Checkboxes and Deletion
Managing the completion status of tasks was another fun challenge. By using Vue’s v-model
to bind the checkbox to each task’s checked
property, I was able to make each task reactive to user input:
<input type="checkbox" class="form-check-input" :id="index" v-model="todo.checked" @change="changeTaskStatus($event)">
This allowed me to visually update the task’s status with a strikethrough when it was checked:
<label class="form-check-label" :style="{'text-decoration': todo.checked ? 'line-through' : 'none'}">{{todo.name}}</label>
For task deletion, I wanted to make sure users didn’t accidentally delete tasks, so I integrated SweetAlert2 to confirm the deletion:
const deleteTask = (taskId) => {
Swal.fire({
title: "Are you sure you want to delete the task?",
showDenyButton: true,
confirmButtonText: "Yes, delete",
denyButtonText: `No`
}).then((result) => {
if (result.isConfirmed) {
tasks.value.splice(taskId, 1);
updateLocalStorage();
Swal.fire("Deleted!", "", "success");
} else {
Swal.fire("Task not deleted", "", "info");
}
});
};
This was a good opportunity to explore how Vue can easily interact with external libraries like SweetAlert2 for enhanced user experience.
5. Leveraging v-for
for Task Iteration
Displaying multiple tasks was another instance where Vue’s v-for
directive came in handy. This allowed me to loop through the tasks
array and render each task dynamically:
<div v-for="(todo, index) in tasks" :key="index">
<div class="form-group form-check d-flex align-items-center">
<input type="checkbox" class="form-check-input" :value=todo.name :id="index" v-model="todo.checked" @change="changeTaskStatus($event)">
<label class="form-check-label" :style="{'text-decoration': todo.checked ? 'line-through' : none}">{{todo.name}}</label>
<span class="ml-auto text-danger" @click="deleteTask(index)">🗑️</span>
</div>
</div>
This directive was key to ensuring that each task was properly rendered, updated, and managed in a way that was reactive to user interaction.
Final Thoughts: What I’ve Gained from This Project
This to-do list project deepened my understanding of Vue’s reactivity system, especially when working with forms and local storage. It’s amazing how Vue makes tasks like managing form submissions, updating the UI based on user interactions, and persisting data so much easier than if I were to do it manually.
In particular, learning how to:
- Prevent form submissions while ensuring reactive data binding with
v-model
- Manage tasks and local storage for persistence
- Use external libraries like SweetAlert2 for improved UX
...has given me confidence in Vue’s capabilities. If you’re new to Vue or just looking for a fun project, a to-do list is a great way to get more comfortable with core concepts like reactivity, form handling, and lifecycle hooks.
Happy coding!
Top comments (0)