I am making a to-do/task list app. I have written a function that is meant to delete an object from the taskList array. async deleteTask(taskList, index){taskList = await taskList.splice(index,1)} I can see in local storage that it deletes the proper object from the array, however in the render <template x-for="(task, index) in filteredTasks" :key="index"> it deletes the final entry on the list, rather than the one I've tried to delete (despite the proper one being deleted in localStorage).
It is rendering based on filteredTasks rather than taskList - but I've told filteredTasks to update after the delete function is run. <button aria-label="Delete Task" x-on:click=" await deleteTask(taskList, index); filteredTasks = [...taskList]">
If I refresh the page, or add a new task to the list, the render corrects itself.
Here is my overall code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Task List</title>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,[email protected],100..700,0..1,-50..200"
/>
<!-- <script src="https://cdn.tailwindcss.com"></script> -->
<script
defer
src="https://cdn.jsdelivr.net/npm/@alpinejs/[email protected]/dist/cdn.min.js"
></script>
<script
defer
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"
></script>
<script>
function methods() {
return {
async pushTask(taskList) {
if (document.getElementById("taskTitleInput").value === "") {
document.getElementById("taskTitleInput").value = "No Title";
}
if (document.getElementById("taskDescriptionInput").value === "") {
document.getElementById("taskDescriptionInput").value =
"No Description";
}
taskList.push({
title: document.getElementById("taskTitleInput").value,
description: document.getElementById("taskDescriptionInput")
.value,
completed: false,
});
document.getElementById("taskTitleInput").value = "";
document.getElementById("taskDescriptionInput").value = "";
},
async firstTask(taskList) {
if (taskList.length === 0) {
taskList.push({
title: "Add a Task",
description: "Add your first task to get started!",
completed: false,
});
}
},
async deleteTask(taskList, index){
taskList = await taskList.splice(index,1)
}
};
}
</script>
<style>
[x-cloak] {
display: none;
}
</style>
</head>
<body x-data="methods()">
<h1>Personal Task Manager</h1>
<div
aria-label="Task Manager App"
x-data="{ taskList: $persist([]), filteredTasks: [] }"
x-directives="{persist: $persist}"
>
<form aria-label="New Task Form">
<label for="taskTitle">Task title:</label
><input type="text" id="taskTitleInput" name="taskTitle" value="" />
<label for="taskDescription">Task description:</label
><input
type="text"
id="taskDescriptionInput"
name="taskDescription"
value=""
/>
<button
x-on:click="await pushTask(taskList); $nextTick(()=>{filteredTasks = [...taskList]})"
>
Add Task
</button>
</form>
<div
aria-label="Task List"
x-init="$nextTick(()=>{filteredTasks = [... taskList]})"
>
<div aria-label="Filters">
<button aria-label="All" x-on:click="filteredTasks = [...taskList]">
All</button
><button
aria-label="Incomplete"
x-on:click="filteredTasks = taskList.filter(task => task.completed === false
)"
>
Incomplete</button
><button
aria-label="Complete"
x-on:click="filteredTasks = taskList.filter(task => task.completed === true)"
>
Complete
</button>
</div>
<!-- Another div with additional features here -->
<div aria-label="Tasks" x-init="firstTask(taskList)">
<template x-for="(task, index) in filteredTasks" :key="index">
<div x-data="{open: false}">
<div x-show="!open">
<input
x-on:click="task.completed = !task.completed"
type="checkbox"
x-bind:id="`checkbox-${index}`"
x-bind:value="task.completed"
/>
<h3 x-text="task.title"></h3>
<p x-text="task.description"></p>
</div>
<form x-show="open">
<label for="editTitle">Edit title:</label
><input
type="text"
x-bind:id="`editTitleInput-${index}`"
name="editTitle"
x-model:placeholder="task.title"
/>
<label for="editDescription">Edit description:</label
><input
type="text"
x-bind:id="`editDescriptionInput-${index}`"
name="editDescription"
x-model="task.description"
/>
<button
x-on:click="open = false; $nextTick(()=>{filteredTasks = [...taskList]})"
>
Submit Changes
</button>
</form>
<button x-on:click="open = ! open" aria-label="Edit Task">
<span class="material-symbols-outlined"> edit </span>
</button>
<button
aria-label="Delete Task"
x-on:click=" await deleteTask(taskList, index);
filteredTasks = [...taskList]"
>
<span class="material-symbols-outlined"> close </span>
</button>
</div>
</template>
</div>
</div>
</div>
</body>
</html>
I have tried:
Having the function in line rather than as a method - it did the same thing. I changed it to a method specifcally so I can try and explicitly define it as an async function that needs to be awaited, but it didn't help.
Making the x-for render based on taskList rather than filteredTasks - it does the same exact thing.
Making the function filter out the specific task rather than using splice. It has the exact same rendering problem.