Vuetify 3 TypeScript Tutorial Series -Part 6
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 leastMIN_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 typeTaskCreateRequest
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 therequest
object with form values and emits a 'create-new-task' event with therequest
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 typeTaskCreateRequest
containing the new task's data.isLoading
: ARef<boolean>
indicating the loading state of the task creation process.isNetworkError
: ARef<boolean>
flag to indicate if there was a network error.axiosError
: ARef<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
totrue
andisNetworkError
tofalse
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 resetsisLoading
tofalse
and navigates to the tasks view usingnavigateToTasksView()
.
Error Handling:
- If an error occurs (
.catch()
clause), it logs the error usinglogRequestError
, setsaxiosError
to the encountered error (if it's an AxiosError), and setsisNetworkError
totrue
.
Finalization:
- In the
.finally()
clause,isLoading
is set tofalse
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
) fromuseTaskNavigation
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 aTaskCreateRequest
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.vue
component. 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:
You should hopefully see a form field where you can enter stuff:
After clicking on submit you should see again the list of all open tasks: