Vuetify 3 TypeScript Tutorial Series -Part 6

Habibi Coding | حبيبي كودنق
Nerd For Tech
Published in
6 min readFeb 8, 2024
tutorial banner

In Part 5 of this tutorial series, we covered the following steps:

  • Created TaskCard
  • Created file for formatting dates
  • Removed unnecessary files
  • Created first page

If you missed Part 5, you can find it here: Part 5

Implement a form for task creation

In src -> components -> create the file TaskCreateForm.vue and add these lines:

<script setup lang="ts">
import {reactive, ref} from 'vue'
import {useField, useForm} from 'vee-validate'
import {MIN_TASK_DESCRIPTION} from "@/constants/appConstants";
import {Priority, TaskCreateRequest} from "@/dtos/taskDto";

const {handleSubmit, handleReset} = useForm({
validationSchema: {
description(value) {
if (value?.length >= MIN_TASK_DESCRIPTION) return true
return 'Description needs to be at least 3 characters.'
},
select(value) {
if (value) return true
return 'Select a priority!.'
},
},
})

const description = useField('description')
const select = useField('select')
const isReminderSet = useField('isReminderSet')

const priority = ref([
Priority[Priority.LOW],
Priority[Priority.MEDIUM],
Priority[Priority.HIGH],
])

const request = reactive<TaskCreateRequest>({
description: description.value.value,
isReminderSet: isReminderSet.value.value,
isTaskOpen: true,
priority: select.value.value,
});

const emit = defineEmits(['create-new-task']);

const submit = handleSubmit(values => {
request.description = values.description;
request.isReminderSet = values.isReminderSet !== undefined;
request.priority = values.select;
emit('create-new-task', request);
})

</script>

Form Validation:

  • useForm initializes form validation with a schema.
  • description: Checks if the description length is at least MIN_TASK_DESCRIPTION characters.
  • select: Ensures a priority is selected.

Field Initialization:

  • useField initializes form fields for description, priority selection, and reminder setting.

Priority Options:

  • priority: A reactive reference to an array of priority options (Low, Medium, High).

Task Creation Request:

  • request: A reactive object of type TaskCreateRequest that holds the form data.

Event Emit Definition:

  • defineEmits(['create-new-task']): Allows the component to emit an event when a new task is created.

Form Submission Handling:

  • submit: A function that handles form submission. It updates the request object with form values and emits a 'create-new-task' event with the request data.

Overall, this script part allows users to create new tasks with a description, priority, and reminder setting. The form fields are validated, and on successful submission, an event is emitted with the task data.

Then add the HTML structure:

<template>
<form @submit.prevent="submit">
<v-text-field
v-model="description.value.value"
:counter="10"
:error-messages="description.errorMessage.value"
label="Description"
/>

<v-checkbox
v-model="isReminderSet.value.value"
:error-messages="isReminderSet.errorMessage.value"
value="1"
label="Reminder"
type="checkbox"
/>

<v-select
v-model="select.value.value"
:items="priority"
:error-messages="select.errorMessage.value"
label="Priority"
/>

<v-btn class="mb-4 clear-btn" @click="handleReset">clear</v-btn>

<v-btn class="mb-4 submit-btn" type="submit">submit</v-btn>
</form>
</template>

After that create the file generateTask.ts in composables package:

import {Ref} from "vue";
import {AxiosError} from "axios";
import {taskService} from "@/services/taskApi";
import logRequestError from "@/composables/logRequestError";
import {TaskCreateRequest} from "@/dtos/taskDto";

export async function generateTask(
request: TaskCreateRequest,
isLoading: Ref<boolean>,
isNetworkError: Ref<boolean>,
axiosError: Ref<AxiosError | unknown>,
navigateToTasksView: () => void
): Promise<void> {
isLoading.value = true;
isNetworkError.value = false;
await taskService.createTask(request)
.then(() => {
isLoading.value = false;
navigateToTasksView();
})
.catch((err: AxiosError | unknown) => {
logRequestError('createNewTask', err);
axiosError.value = err instanceof AxiosError ? err : undefined;
isNetworkError.value = true;
})
.finally(() => {
isLoading.value = false;
});
}

Function Parameters:

  • request: An object of type TaskCreateRequest containing the new task's data.
  • isLoading: A Ref<boolean> indicating the loading state of the task creation process.
  • isNetworkError: A Ref<boolean> flag to indicate if there was a network error.
  • axiosError: A Ref<AxiosError | unknown> to store any Axios error encountered during the API call.
  • navigateToTasksView: A function that navigates the user to the tasks view upon successful task creation.

Starting the Task Creation Process:

  • Sets isLoading to true and isNetworkError to false to indicate the start of the process.

API Call:

  • Calls taskService.createTask(request) to create a new task with the provided request data.

Success Handling:

  • On successful task creation (.then() clause), it resets isLoading to false and navigates to the tasks view using navigateToTasksView().

Error Handling:

  • If an error occurs (.catch() clause), it logs the error using logRequestError, sets axiosError to the encountered error (if it's an AxiosError), and sets isNetworkError to true.

Finalization:

  • In the .finally() clause, isLoading is set to false to indicate the completion of the process, regardless of success or failure.

This function encapsulates the logic for creating a task and managing related states and side effects in an application.

Okay, it's time to create a new page to show the creation of a task in the UI. In pages create the file TaskCreatePage.vue and add these lines:

<script setup lang="ts">
import {useTaskNavigation} from '@/composables/useTaskNavigation';
import {ref} from "vue";
import {AxiosError} from "axios";
import {generateTask} from "@/composables/generateTask";
import {TaskCreateRequest} from "@/dtos/taskDto";
import MainBackground from "@/components/MainBackground.vue";
import LoadingSpinner from "@/components/LoadingSpinner.vue";
import ErrorDialog from "@/components/ErrorDialog.vue";
import Navbar from "@/components/Navbar.vue";
import TaskCreateCard from "@/components/TaskCreateCard.vue";


const {handleTaskTypeSelected, logoClicked, navigateToTasksView} = useTaskNavigation();
const isLoading = ref(false);
const isNetworkError = ref(false);
const axiosError = ref<AxiosError>();


const createTask = (request: TaskCreateRequest) => {
generateTask(request, isLoading, isNetworkError, axiosError, navigateToTasksView);
};

</script>

Task Navigation Setup:

  • Destructuring methods (handleTaskTypeSelected, logoClicked, navigateToTasksView) from useTaskNavigation composable for handling task-related navigation.

Reactive State:

  • Declaring reactive references (isLoading, isNetworkError, axiosError) to track the state of the task creation process.

Task Creation Method:

  • Defining a method createTask that takes a TaskCreateRequest object as an argument.
  • When called, it invokes generateTask with the request data and the reactive state references. This method handles the actual task creation logic, including updating the loading state, error handling, and navigation on successful creation.

In summary, this script setup block orchestrates the task creation process in a TaskCreateCard.vuecomponent. It uses composables for complex logic and state management, and components for displaying the UI elements like loading spinners and error dialogs. The component interacts with the useTaskNavigation composable for navigation and uses generateTask for the task creation logic, handling loading states and errors.

Next, add the HTML structure for the page:

<template>
<Navbar @task-type-selected="handleTaskTypeSelected" @logo-clicked="logoClicked"/>
<MainBackground>
<TaskCreateForm @create-new-task="createTask"/>
<LoadingSpinner :is-loading="isLoading"/>
<ErrorDialog :model-value="isNetworkError" :axios-error="axiosError"/>
</MainBackground>
</template>

We need to handle the routing now, so open in router packge the file index.ts and make sure it looks like this:

// Composables
import {createRouter, createWebHistory} from 'vue-router'
import {HOME_VIEW, TASK_CREATE_VIEW} from "@/constants/appConstants";
import TaskCreatePage from "@/pages/TaskCreatePage.vue";
import TasksOverviewPage from "@/pages/TasksOverviewPage.vue";


const routes = [
{
path: '/',
component: () => import('@/layouts/default.vue'),
children: [
{
path: "",
name: HOME_VIEW,
component: TasksOverviewPage,
props: true
},
{
path: 'new-task',
name: TASK_CREATE_VIEW,
component: TaskCreatePage,
props: true
}
],
}
]

const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})

export default router

We added the {} object for creating a new task by adding :


path: 'new-task',
name: TASK_CREATE_VIEW,
component: TaskCreatePage,
props: true

Then run the project again with:

pnpm dev

Click on the “Create Task” button:

Create a new task

You should hopefully see a form field where you can enter stuff:

submit new task

After clicking on submit you should see again the list of all open tasks:

list of open tasks

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 7

Don’t forget to check out the video playlist on YouTube.

Here is the source code on GitHub, check out the branch: part-six

--

--