Building Web Apps with Vue 3 composition API + Typescript + Vuex(4.0)

Building Web Apps with Vue 3 composition API + Typescript + Vuex(4.0)

Building Web Apps with Vue 3 composition API + Typescript + Vuex(4.0)

Introduction

In this tutorial, we are going to create a task management application to demonstrate how to build applications with the new Vue 3 composition API, typescript, and Vuex(4.0). Furthermore, we will explore Vuex(4.0) practically.

Pre-requisite

Node.js 10.x and above
Knowledge of JavaScript Es6
Basic knowledge of vue.js
Basic knowledge of Typescript

Introducing Vue composition API

The composition API is a new API for creating components in vue 3.
It presents a clean and flexible way to compose logic inside and between components. Also, it solves the problems associated with using mixins and higher-order component to share re-usable logic between components. It has a small bundle size and naturally supports typescript.

Introducing Vuex(4.0)

Vuex is a state management library created by the Vue team and built solely for use with Vue. It provides an intuitive development experience when integrated into an existing Vue app. Vuex becomes crucial when your Vue app gets more complex as it grows.

The latest version of Vuex, version v4.0.0 supports the new Composition API introduced in Vue 3 as well as a more robust inference for TypeScript.
In this article, we will explore the latest version of Vuex, version v4.0.0 practically as we build along.

Setting up vue 3 + vuex + TypeScript App

Let’s start by creating a Vue 3 app with typescript support using the Vue-CLI tool.

Take the following steps to create a Vue 3 + Vuex app with Typescript support, using Vue CLI tool

  1. Install the Vue-CLI tool globally by executing the command below:

2. If you have the CLI already you can check if you have the appropriate version (v4.x):

3.  Make sure you upgrade it to the latest version with the following command:

Create a new Vue 3 app with typescript and Vuex support with the command below:

Choose the manually select feature option
Hit the space key to select the following options:

  • choose Vue version
  • Babel
  • TypeScript
  • Linter / Formatter

Next, Choose Vue 3.x(Preview) as the version for the project.

  • Enter yes to use the class-style component syntax
  • Enter yes to use Babel alongside TypeScript
  • Select any linter of your choice
Project setup options

Project setup options

Your final setup options should be like the above.

Once the Vue 3 app is generated successfully, we will have a Vue 3 project setup with vuex (V4.x) and full typescript support.

To start the development server, run the command below in your terminal and head over to http://localhost:8080 to preview your project in the browser.

Result of Initial project scaffold

Result of Initial project scaffold

Next, open the vuex-ts-project folder in VS code editor or any other code editor with typescript support.

For developing Vue applications with TypeScript, the Vue core team strongly recommends using Visual Studio Code or WebStorm which provides great out-of-the-box support for TypeScript and Vue. Hence in this tutorial, we will use Visual Studio Code.

Overview of what we will build in this tutorial

In this tutorial we will build a task management application with the following features:

  • Create task
  • Edit task
  • View task
  • Delete task

Dependency Installation

We will use the Bulma CSS framework for styling our application. Run the command below to install Bulma CSS:

Head over to App.vue and import Bulma CSS as follows:

Setting up our Vuex store

In Vuex, the state of the application is kept in the store. The application state can only be updated by dispatching the actions within a component which triggers the mutations in the store. The Vuex store is made up of the state, mutations, actions, and getters.

We’ll start building our application by first building the Vuex store with TypeScript for type safety. We already know that a fully-defined Vuex store is composed of 4 distinct pieces – state, mutations, actions, and getters. We’ll set up the store in steps:

  1. Create our state objects
  2. Set up the mutations that will occur in our application
  3.  Create the actions that will commit to these subsequent mutations
  4. Create getters for components to directly compute state data

State

A state is a store object that holds the application-level data that needs to be shared between components.

Head over to the store folder and create a state.ts file inside it. Add the following code below to state.ts:

Here, we add some type of safety to the TaskItem and State by defining their types. We also export types because they will be used in the definitions of getters, mutations and actions. Finally, we cast the State type to the state.

Mutations

Mutations are methods that modify the store state. It accepts state as the first argument and payload as the second, and eventually modifies the store state with the payload.
To create mutations, we will use constants for mutation types as recommended by the Vuex docs.

Still in the store folder, create a mutations.ts file with the following code below:

MutationTree is a generic type, that is shipped with the Vuex package. it helps to declare a type of mutation tree. We will use it as we proceed.
Here, all of our possible names of mutations are stored in the MutationTypes enum.

Next, we will declare a contract (types) for each MutationType
Add the code below to the mutation.ts file:

Next, we will implement the contract (types) declared for each MutationType :
Add the code below to the mutation.ts file:

Here, we create the mutations variable which stores all the implemented mutations. The MutationTree<State> & Mutations ensures that the contract is implemented correctly else Typescript will trigger an error.

Let’s assume that we failed to implement the contract below:

By adding comments the mutation as seen below:

Here typescript IntelliSense will highlight const mutations in red and Hovering on the highlight we will get the following typescript error:

Actions

Actions are methods that trigger the mutations. When handling asynchronous tasks, actions are used before calling the corresponding mutations. We will simulate asynchronous tasks as we create our actions.

Still in the store folder, create an actions.ts file with the following code below:

Similarly, all of our possible names of actions is stored in the ActionTypes enum.
Next, we will declare a contract (types) for each ActionType

Add the code below to the action.ts file:

Here we use the ActionContext type which is shipped with the Vuex package, in the
ActionAugments type to restrict commits only to the declared mutations and also to check the payload type.

Next, we will implement the contract (types) declared for each ActionType
Add the code below to the action.ts file:

Here we create a sleep variable that returns Promise.  Followed by the actions variable which stores all the implemented actions. Similarly, the ActionTree<State> & Actions ensures that the contract (type Actions) is implemented correctly else Typescript will trigger an error.

Next, we set up our asynchronous call in the GetTaskItems action and commit an array containing a single task object. we also set up SetCreateModal and SetEditModal actions.

Getters

Getters are methods that receive the state as its first parameter and return computed information from the store state.
The only getters we’ll need in our store are:

  • completedTaskCount – A function that gets the total number of completed task in our state
  • totalTaskCount – A function that gets the total number of task in our state
  • getTaskById – A function that gets a task by its id.

Still in the store folder, create a getters.ts file with the following code below:

Similarly, we have added some type of safety to Getters.

Store

Now that our state, mutations, actions, and getters are all set-up, let’s wire them to the global Vuex store.
The main Vuex store now needs to import these pieces ( state, mutations, actions, and getters) and include them within the createStore method.

We’ll update store/index.ts to reflect this:

Here we create a store using the createStore() method from Vuex. We trigger the createLogger plugin which logs the state (previous state and next state) and mutations to the console. Export the useStore function for use in the application components.
Now we have access to a fully typed store all through our application.

To have the store accessible in all our application’s components, we need to inject it into the entire application. Fortunately, the Vue-CLI tool had already imported the entire store and passed it within the application’s Vue instance.

Connecting the component’s logic with the Vuex store

Now that the store is properly set up with full type support we will look at how to use the store in our app components using the composition API.

App

For us to retrieve information from the store, we first need to invoke the mutation that sets the hardcoded task in the store. Since we need the mutation to occur at the moment the component is mounted, we’ll dispatch the GetTaskItems action within the component’s mounted() hook.
In order to use the store in a component, we must access it via the useStore hook, which will return our store.

We will create three computed properties:

  •  completedCount – that calls the completedTaskCount getter method to retrieve the total number of completed tasks
  • totalCount – that calls the totalTaskCount getter method to retrieve the total number of tasks
  • loading – that gets the loading property of the state

Update App.vue with the following code below:

Notice how we return loading, completedCount and totalCount. This is because we need to access them in the template. If we fail to return a method that is accessed in the template, Vue will trigger some errors.

TaskList

In order to launch the modal responsible for creating a task, we first need to create a method (setModal) that invokes the mutation that sets showCreateModal in the state to true.
We will create six computed properties:

  •  showCreateModal – that gets the showCreateModal property of the state.
  • showEditModal – that gets the showEditModal property of the state.
  • showTaskModal – that gets the showTaskModal property of the state.
  • tasks – that gets the list of tasks from the state.
  • showTaskId – that gets the showTaskId property of the state.
  • editTaskId – that gets the editTaskId property of the state.

Create TaskList.vue inside the components folder with the following code below:

Here the TaskList component houses the CreateModal, TaskListItem, TaskItem, and the EditModal components. It passes edidtaskId and showtaskId as props to EditModal and TaskItem respectively. We use the v-for directive to render the TaskListItem component for every task in the computed tasks property. We’ve passed each task as a task prop for every iterated TaskListItem component. We’ve used task.id as the unique key identifier. Our application will throw an error until we create all the children components in TaskList the component.

TaskListItem

This component receives the task props from every iteration in the TaskList component and renders the list of these task properties in the browser.

We will create four methods to mutate our store’s properties:

  • toggleCompletion – that toggles the completed property of a task.
  • removeTask – that commits the RemoveTask mutation (which removes a task from the state by its id ) with an id object as payload.
  • viewTask – that commits the SetTaskModal mutation (which allows us to view a single task).
  • editTask – that commits the SetEditModal mutation (which allows us to edit a single task).

Within the components folder, let’s create a new TaskListItem.vue file:

TaskItem

This component receives the task id props from the TaskList component. The id props are used to fetch a task with the corresponding id via the getTaskById getters method and render these task properties in the browser.

We will create a computed property:

  • task – that calls the getTaskById getter method to retrieve a task by its Id

Also, we will create a method:

  • closeModal – that commits the SetTaskModal mutation which sets the showModal property in the state to false.

Within the components folder, let’s create a new TaskItem.vue file:

CreateModal

This component is responsible for creating tasks.
Hence we will create two methods to mutate our state:

  • createTask – that commits the CreateTask mutation (which adds a new task to the tasks property in the state).
  • closeModal – that commits the SetTaskModal mutation (which sets showModal property in the state to false).

Within the components folder, let’s create a new CreateModal.vue file:

Notice how we used vue3’s reactive method to store values that is used in the template.
We could create a variable as we normally do inside a setup function and add it to the returned object, then render it in the template. This will work but there will be no reactivity. Also we could use refs but then, it is used on primitives (strings, numbers, booleans).
When using reactive, we need to use toRefs to convert the reactive object to a plain object, where each property on the resulting object is a ref pointing to the corresponding property in the original object.

EditModal

This component is responsible for updating tasks. Its logic is similar to the CreateTask component we just discussed above.
Hence we will create two methods to mutate our state:

  • updateTask – that commits the UpdateTask mutation (which modifies a task with a corresponding id in the task’s property of the state).
  • closeModal – that commits the SetTaskModal mutation (which sets showModal property in the state to false).

Within the components folder, let’s create a new EditModal.vue file:

In order to get the task properties already displayed in the form fields when the component is mounted, we had to call the setFields method on the onMounted lifecycle hook. The setFields triggers the getTaskById getters method to fetch a task from the state by its id, then update the properties in the reactive object with the fetched task properties.

Now you can run the application with:

Conclusion

We have used most of Vuex 4 major feature releases including its robust typing for the store and excellent integration with the Composition API in Vue 3 to build a dynamic task management application. We have practically explored the features of the Vue 3 composition API.

Also, we have seen how the createLogger function is used to log the mutations and state to the browser’s console making debugging the store easy even without the Vue developer tool.

I hope you have learned a great deal from this tutorial. Do reach out in the comment section below if you have any questions or suggestions.

Here is the github repo for the task management application.

About the author

Stay Informed

It's important to keep up
with industry - subscribe!

Stay Informed

Looks good!
Please enter the correct name.
Please enter the correct email.
Looks good!

Related articles

26.02.2024

Server-Side Rendering with Vue.js and Nuxt.js

Vue.js and Nuxt.js seamlessly complement each other for SSR. Vue.js provides the core functionality for dynamic UIs, while Nuxt.js enables ...

An Introduction to Pinia: The Alternative State Management Library for Vue.js Applications

The maintenance of the application’s data and ensuring that other components can access and modify the data as necessary depending on state ...

21.01.2021

Writing end-to-end tests for Nuxt apps using jsdom and AVA

Testing is a very important aspect of web application development. It can help reveal bugs before they appear and also instill confidence in your web ...

9 comments

Janet John December 4, 2020 at 12:36 pm
0

Very insightful article

 
Emmanuel John December 4, 2020 at 8:55 pm
0

Thank you Janet. I am glad you found it useful.

ql1993xl December 13, 2020 at 12:57 pm
0

easy to understand, thanks

 
Emmanuel John December 14, 2020 at 6:12 am
0

Nice 👍

Jan Junior Everaert February 15, 2021 at 12:52 am
0

Thanks for this great article. Is it possible to have different modules to?

 
Emmanuel John February 19, 2021 at 5:51 pm
0

You’re welcome Jan! Yes it is possible

Jean Hernandez March 20, 2021 at 11:50 pm
0

This is an incredible and informative post, it’s practically a course by itself. Thanks a lot!

 
Emmanuel John March 21, 2021 at 5:04 am
1

You’re welcome Jean

Dennis Hasenpflug August 19, 2021 at 3:30 pm
0

Very helpfull, thank you!
One question: Do you see any problem in making the payload parameter in the ActionAugments type nullable?
For one of my actions i dont need any payload and just want to fire a mutation but i get the error that payload wasnt provided.
I can get rid of this error by doing “payload?: Parameters[1]” just wanted to confirm somehwere if this is the right way or if i might dont see something 🙂
Thanks in advance!

Sign in

Forgot password?

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy

Password recovery

You can also try to

Or use a social network account

 

By Signing In \ Signing Up, you agree to our privacy policy