Ready for Vue 3? Here’s how to ease the transition

By Jackson Noël on Jun 23, 2022 18 min read

When we work with clients at Zeitspace, one thing we're always doing is looking ahead. What is coming down the pipeline in terms of software development and what can we do now to make that transition easier? On a recent project with Kitchener Waterloo Community Foundation (KWCF) we did just that when we decided to update the code base to use the Vue 2 Composition API plugin to ease the transition to Vue 3.

The Composition API allows you to organize your code by functionality instead of it being separated into variables, methods, and computed properties. It also gets rid of the  this  keyword which allows you to use arrow functions without worrying about what  this  refers to. The Composition API also introduces composables, which allow you to easily and cleanly reuse code and don’t have the same pitfalls that the Options API’s mixins did such as naming conflicts and implicit dependencies. 

Even though Vue 3 was officially launched in September 2020, a lot of major libraries have been slow to upgrade to be compatible with Vue 3. Vuetify 3, the newest version of a major Vue component library, was set to launch May 2022, (it hasn’t yet), which will add support for the Composition API allowing more people to make the switch to Vue 3. Our KWCF project uses Vuetify, which is why we didn’t just update to Vue 3 right away and instead opted for the Composition API plugin. The Composition API plugin allows you to use Vue 3’s Composition API in Vue 2 which can help ease the transition to Vue 3.

To demonstrate how we used the plugin, I created a little todo list application that we’ll convert from Vue 2's Options API to Vue 3's Composition API using the Composition API plugin. The app uses Node 16, MongoDB, and Vue. Grab the front end from here:

 git clone

and backend here:

 git clone 

To set up the back end, make sure MongoDB is running ( and then run:

 cd vuedo-backend 

 npm i 

Note: The next command will drop your  todo  database if you already have one. You can modify the  cleanstart  script to use another database name instead of  todo  to avoid this. You will also have to change the  dbConfig  property in  src/config/dev.json  to connect to the name you choose.

 npm run cleanstart 

To get started, enter the vuedo repo with  cd vuedo  in a new terminal instance and we’ll install the Vue composition API plugin. We’ll also be converting our vuex store so that it’s  compatible with the Composition API, so we’ll install the vuex composition helpers as well:

 npm i @vue/composition-api 

npm i vuex-composition-helpers 

We must then import and install the plugin with Vue.use before we can use the Composition API in the app. We do this by adding these lines to our main.ts file

import VueCompositionAPI from '@vue/composition-api'


Now that we’re set up, we can convert our first component to use the Composition API.

We’ll start by converting  components/AddTodoModal.vue . The Composition API uses  defineComponent  instead of  Vue.extend  so we’ll start the conversion by importing it and using it to define the component.

import { defineComponent, ref } from '@vue/composition-api'

export default defineComponent({

The setup function

The setup function is the entry point for Composition API use in Vue. Within it, you can define methods and variables together and then return them to the template. The setup function has two parameters,  props and  context . The props parameter functions almost the same way as  this.props  did before except it is  now accessed with  props.propName  instead of  this.propName  since there is no more  this  keyword in the Composition API. Context is the second parameter that’s used to access other values that you may be familiar with from the Options API, such as attrs, slots, emit, and expose, that would have been accessed through  this . We will use context later to access the Vue router.

Converting the data section

We can move the variables from our data section and put them in the setup function. To keep our variables reactive, we need to wrap them in a “ref”. A ref is a reactive wrapper that has one property which can be accessed with the  .value  property. To do this, add a setup function within defineComponent like this:

We can move the variables from our data section and put them in the setup function. To keep our variables reactive, we need to wrap them in a “ref”. A ref is a reactive wrapper that has one property which can be accessed with the  .value  property. To do this, add a setup function within defineComponent like this: 

export default defineComponent({

 name: 'AddTodoModal',

 setup() {
  const addTodoModal = ref(false)
  const title = ref('')
const description = ref('')

As you can see, you can define the default value for a ref by passing it to the ref's constructor. If you need to declare a type for an object or array variable, you can also do that while passing a default value to the ref:

const someObject = ref({} as SomeType)

If we want to be able to access these variables in the template we must also return them within the setup function:

export default defineComponent({

 name: 'AddTodoModal',

 setup() {
   const addTodoModal = ref(false)
   const title = ref('')
   const description = ref('')

   return {


An alternate way to declare reactive variables would be to use the “reactive” method. You can use this if you would prefer to have either all or some of your state variables be a reactive object. With a reactive object, you don’t have to define each property as an individual ref and can access the property with  form.title  instead of  title.value . You can do this in  src/views/TodoItem  as an example:

const form = reactive({
     title: props.title,
     description: props.description

Converting the Vuex store

We’ll also update how we access the Vuex store so let's import the vuex composition helpers and create a namespaced helper for the todos store. To access the vuex actions, we will use the useActions  hook. This will replace  mapActions  in the computed section.

import { createNamespacedHelpers } from 'vuex-composition-helpers'

const { useActions } = createNamespacedHelpers('todo')

Note: we can rename  useActions  to  useTodoActions . This is generally good practice to increase readability and to allow adding other namespaced helpers without conflicting variable names.

const { useActions: useTodoActions } = createNamespacedHelpers('todo')

Then we can remove  mapActions  and replace it by adding  useTodoActions  within our setup method like this:

const { createTodo } = useTodoActions(['createTodo'])

to access the createTodo action.

Converting the methods section

Next we’ll move our methods from the methods section to the setup function. A benefit of having our variables and methods in the same section is it allows us to logically group them by functionality instead of grouping them by variables and methods.

To convert the methods section, copy the  saveTodo  method and paste it in the setup function and then delete the methods section. We need to properly define our methods by adding the  function  keyword after  async . We also need to remove  this.   everywhere since it no longer exists in the Composition API and append  .value  when accessing the value of refs.

async function saveTodo(): Promise<void> {

     await createTodo({ title: title.value, description: description.value })

     addTodoModal.value = false



We could also define this same function as an arrow function and still access our state variables with no issue now that the  this  keyword is no longer used:

   const saveTodo = async (): Promise<void> => {
     await createTodo({ title: title.value, description: description.value })
     addTodoModal.value = false

We also have to return the saveTodo method so that we can call it from the template.

We’re finished converting the AddTodoModal component. Let’s take a look at the  views/TodoList.vue  view.

Each of the lifecycle methods are also replaced with lifecycle hooks. Import the onMounted hook from @vue/composition-api and use it like this to replace the old ‘mounted’ method:

   const { getTodos, toggleDone } = useTodoActions(['getTodos', 'toggleDone'])

   onMounted(async (): Promise<void> => {
     await getTodos()

Converting the computed section

Computed values function the same way as before but instead of being in their own section they are also in the setup function and use the  computed   hook. Just like how we mapped our data variables to refs, we can import the  computed  method from the Composition API plugin and use it to create computed variables. These variables' value also needs to be accessed with the  .value  property. 

We first need to access our Vuex state variables by using the useState hook just like we accessed actions before with the useActions hook. 

const { useActions: useTodoActions, useState: useTodoState } = createNamespacedHelpers('todo')

export default defineComponent({

 components: {
 setup(props, context) {
   const { todos } = useTodoState(['todos'])

We can then use the computed method to compute which todos are done like this:

const doneTodos = computed(() => todos.value.filter((todo: Todo) => todo.done))

You now know everything you need to know to convert the rest of the app and can try it yourself or look at the composition-api branch for the test project on our GitHub page to see a completed version.

After converting your code base, you can take advantage of some of the powerful features of the Composition API such as composables. With a composable we could take similar logic from all three todo lists to create a single set of code they can all share. Take a look at how to use them here. As more libraries start to support Vue3's Composition API, it's a good time to start updating your code base to ease the transition and start taking advantage of some of the Composition API's powerful features, such as better code organization, easier use of arrow functions, and composables.

Jackson Noël

Written by Jackson Noël

Jackson Noël is a software developer at Zeitspace.