Vuetify 3 TypeScript Tutorial Series -Part 8
In Part 7 of this tutorial series, we covered the following steps:
- Implemented task card details
If you missed Part 7, you can find it here: Part 7
Updating a task
In the previous parts we enabled from the CRUD operations the create and read possibilities. Update and delete are missing, so let us tackle now the update/editing part. In src
-> components
create the file TaskUpdateForm.vue
and add these lines:
<script setup lang="ts">
import {reactive, ref} from "vue";
import {useTaskStore} from "@/store/taskStore";
import {useField} from "vee-validate";
import {Priority, TaskUpdateRequest} from "@/dtos/taskDto";
import {MIN_TASK_DESCRIPTION} from "@/constants/appConstants";
const priority = ref([
Priority[Priority.LOW],
Priority[Priority.MEDIUM],
Priority[Priority.HIGH],
]);
const taskStore = useTaskStore();
const request = reactive<TaskUpdateRequest>({
description: taskStore.taskToEdit.description,
isReminderSet: taskStore.taskToEdit.isReminderSet,
isTaskOpen: taskStore.taskToEdit.isTaskOpen,
priority: taskStore.taskToEdit.priority,
});
const description = useField('description');
const emit = defineEmits(['updated-task', 'abort-clicked']);
const handleSubmit = () => {
if (request.description.length >= MIN_TASK_DESCRIPTION) {
emit('updated-task', taskStore.taskToEdit.id, request);
} else {
description.errorMessage.value = 'Description needs to be at least 3 characters.';
}
};
const handleReset = () => {
emit('abort-clicked');
};
</script>
Then add the UI structure:
<template>
<form @submit.prevent="handleSubmit">
<v-text-field
v-model="request.description"
:counter="10"
:error-messages="description.errorMessage.value"
label="Description"
/>
<v-checkbox v-model="request.isReminderSet" label="Reminder" type="checkbox"/>
<v-checkbox v-model="request.isTaskOpen" label="Open" type="checkbox"/>
<v-select v-model="request.priority" :items="priority" label="Priority"/>
<v-btn class="mb-4 clear-btn" @click="handleReset">abort</v-btn>
<v-btn class="mb-4 submit-btn" type="submit">update</v-btn>
</form>
</template>
Now, we need a new composable in order to abstract logic for editing a task, so create in package composables
the file editTask.ts
and add these lines:
import {Ref} from "vue";
import {AxiosError} from "axios";
import {taskService} from "@/services/taskApi";
import logRequestError from "@/composables/logRequestError";
import {TaskUpdateRequest} from "@/dtos/taskDto";
export async function editTask(
id: number,
request: TaskUpdateRequest,
isLoading: Ref<boolean>,
isNetworkError: Ref<boolean>,
axiosError: Ref<AxiosError | unknown>,
navigateToTasksView: () => void): Promise<void> {
isLoading.value = true;
isNetworkError.value = false;
await taskService.updateTask(id, request)
.then(() => {
isLoading.value = false;
navigateToTasksView();
})
.catch((err: AxiosError | unknown) => {
logRequestError('updateTask', err);
axiosError.value = err instanceof AxiosError ? err : undefined;
isNetworkError.value = true;
})
.finally(() => {
isLoading.value = false;
});
}
Function Declaration:
editTask
is anasync
function, meaning it can perform asynchronous operations.- It takes several parameters:
id
: The identifier of the task to be updated.request
: An object of typeTaskUpdateRequest
, likely containing the updated task data.isLoading
: ARef
object from Vue's Composition API, used to track whether the task is being processed.isNetworkError
: ARef
object to indicate if there's a network error during the task update process.axiosError
: ARef
to store any error that occurs during the HTTP request.navigateToTasksView
: A function that, when called, navigates the user to a different view, presumably the tasks view.
Function Logic:
- The
isLoading
flag is set totrue
at the beginning, indicating the start of an asynchronous operation. isNetworkError
is initially set tofalse
.- The
taskService.updateTask
method is called withid
andrequest
as arguments. This method likely makes an HTTP request to update the task on a server. .then()
: If the request is successful (i.e., the task is updated),isLoading
is set tofalse
, andnavigateToTasksView
is called to redirect the user..catch()
: If an error occurs, the error handling logic is executed. If the error is an instance ofAxiosError
, it is stored inaxiosError
, andisNetworkError
is set totrue
..finally()
: Regardless of the outcome (success or failure),isLoading
is set tofalse
, signifying the end of the operation.
Purpose:
- This function encapsulates the logic for updating a task. It handles the loading state, error state, successful update scenario, and navigation upon success.
Time to add page for updating a task, so create in pages
the file TaskUpdatePage.vue
and add these lines:
<script setup lang="ts">
import {useTaskNavigation} from "@/composables/useTaskNavigation";
import router from "@/router";
import {ref} from "vue";
import {AxiosError} from "axios";
import {editTask} from "@/composables/editTask";
import {TaskUpdateRequest} from "@/dtos/taskDto";
import Navbar from "@/components/Navbar.vue";
import MainBackground from "@/components/MainBackground.vue";
import TaskUpdateForm from "@/components/TaskUpdateForm.vue";
import LoadingSpinner from "@/components/LoadingSpinner.vue";
import ErrorDialog from "@/components/ErrorDialog.vue";
defineProps({
id: {
type: String,
required: true
}
});
const {handleTaskTypeSelected, logoClicked, navigateToTasksView} = useTaskNavigation();
const isLoading = ref(false);
const isNetworkError = ref(false);
const axiosError = ref<AxiosError>();
const clickedAbort = () => {
router.back();
};
const updateTask = (id: number, request: TaskUpdateRequest) => {
editTask(id, request, isLoading, isNetworkError, axiosError, navigateToTasksView);
};
</script>
Then add the UI structure:
<template>
<Navbar @taskTypeSelected="handleTaskTypeSelected" @logoClicked="logoClicked"/>
<MainBackground>
<TaskUpdateForm @updated-task="updateTask" @abort-clicked="clickedAbort"/>
<LoadingSpinner :is-loading="isLoading"/>
<ErrorDialog :model-value="isNetworkError" :axios-error="axiosError"/>
</MainBackground>
</template>
Lastly, to navigate to the update page we need to adapt in package router
the file index.ts
and add this object:
{
path: 'edit/:id',
name: TASK_UPDATE_VIEW,
component: TaskUpdatePage,
props: true,
}
Start again the project:
pnpm dev
Open http://localhost:3000/ and click on “Edit Task” on an item card:
Then you should see a form to edit your task:
I will now take away the checkmark from “Open” so it goes to the closed tasks and I will adapt the description and of course don’t forget to click on the “update” button.
Click on “Closed Tasks” now:
Notice when you click on “All Tasks” how the open and closed tasks have different border colors:
Implement logic to delete a task
We are almost done with our CRUD application, we need to know enable the delete part. As you might have guessed, we need to create a new file in components
and create the file TaskDeleteDialog.vue
and add these lines:
<script setup lang="ts">
import {ref, watch, defineProps} from 'vue';
const props = defineProps({
modelValue: Boolean,
taskDescription: String
});
const isDialogActive = ref(false);
const emits = defineEmits(['confirm-delete', 'update:modelValue']);
watch(() => props.modelValue, newVal => {
isDialogActive.value = newVal;
});
const confirmDelete = () => {
emits('confirm-delete');
isDialogActive.value = false;
emits('update:modelValue', false);
};
</script>
Define Props:
- Using
defineProps
to define the component's props. modelValue
: A Boolean indicating whether the dialog is visible or not.taskDescription
: A String to hold the description of the task being considered for deletion.
Reactive State Declaration:
isDialogActive
: A reactive reference (ref
) that tracks whether the dialog is open or closed.
Emit Declaration:
emits
: Defines the events that the component can emit to its parent. In this case, it can emitconfirm-delete
andupdate:modelValue
events.
Watch Effect:
- A
watch
effect that monitors changes toprops.modelValue
. Whenprops.modelValue
changes,isDialogActive
is updated to reflect this new value. This synchronizes the component's internal state with the parent's state.
Confirm Delete Method:
confirmDelete
: A method that emits theconfirm-delete
event when the user confirms the deletion action. It also setsisDialogActive
tofalse
(closing the dialog) and emitsupdate:modelValue
withfalse
as the value to update the parent's state, indicating that the dialog should be closed.
Now, add the structure for the UI:
<template>
<v-row justify="center">
<v-dialog v-model="isDialogActive" persistent width="auto">
<v-card>
<v-card-title class="text-h5">
Are you sure you want to delete this task?
</v-card-title>
<v-card-text>
Task Description: {{ props.taskDescription }}
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="red-darken-1"
variant="text"
@click="isDialogActive = false;
emits('update:modelValue', false);">
Cancel
</v-btn>
<v-btn color="green-darken-1" variant="text" @click="confirmDelete">
Confirm
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</template>
With that, we conclude the first part of this tutorial series. If you found it useful and informative, give it a clap. Here is Part 9
Don’t forget to check out the video playlist on YouTube.
Here is the source code on GitHub, check out the branch: part-eight