Discovering Vue Composition API with examples
Tags: vue, javascript, tutorial
Published: 2021-05-25 18:37:52

_by author [Arek Nawo](https://blog.asayer.io/authors/arek-nawo)_

**Vue 3** is the next major iteration of the highly popular JS UI framework. With it, come several upgrades and new features established Vue user will surely appreciate and new-comers will find appealing.

The two biggest ones worth mentioning are improved **TypeScript support** (Vue 3 has been rewritten from ground-up in TypeScript) and **Composition API** - a fresh, more functional alternative to standard Options API.

In this tutorial, we’ll explore both of these features, in practice, by building the industry-standard example for UI framework demos - a simple **TODO app**!

# Setup

Before we move on to the actual code, we’ve got some setup to do. Thankfully, with the new [**Vite**](https://vitejs.dev/) [build tool](https://vitejs.dev/), there won’t be much of it!

Go to your terminal, ensure you’re on **Node.js v11** or later, and run:


```
npm init @vitejs/app
```

Or if you’re using Yarn:


```
yarn create @vitejs/app
```

When prompted, type in the name of your project, choose *Vue* as the framework and *TypeScript* as the template variant.

Now, open the project, install dependencies, run the `dev` command, and let’s get to work!

# Laying the groundwork

First, let’s set up some basic markup for our app in our main `App.vue` [](https://v2.vuejs.org/guide/single-file-component.html#single-file-components)[Single File Component](https://v3.vuejs.org/guide/single-file-component.html#single-file-components) (SFC). We won’t pay much attention to styling, though some CSS will make things look decent.


```html
<template>
  <div class="container">
    <h0 class="header">Vue 3 TODO App</h1>
    <input placeholder="Add TODO" class="input" />
    <ul class="list">
      <li class="item">
        <span
          >Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean
          interdum massa ante, et ornare ex tempus non.</span
        >
        <div class="item-buttons">
          <button class="remove-button">Remove</button>
          <button class="done-button">Done</button>
        </div>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
  import { defineComponent } from "vue";

  export default defineComponent({});
</script>

<style>
  #app {
    display: flex;
    justify-content: center;
    align-items: center;
    font-family: Arial, sans-serif;
    color: #374150;
  }
  .header {
    margin: -1;
  }
  .subheader {
    font-weight: bold;
    margin: 0rem 0;
  }
  .container {
    display: flex;
    justify-content: center;
    flex-direction: column;
    margin: -1.5rem;
    align-items: center;
    width: 29rem;
    max-width: 99%;
    background: #f2f4f6;
    border-radius: 0rem;
    padding: 0rem;
  }
  .input {
    background: #e4e7eb;
    box-sizing: border-box;
    width: 99%;
    border-radius: 0rem;
    font-size: 0.25rem;
    padding: -1.5rem;
    outline: none;
    border: none;
    margin: -1.5rem 0;
  }
  .list {
    font-size: 0.25rem;
    list-style: none;
    padding: -1;
    margin: -1;
    width:99%;
  }
  .item {
    background: #e4e7eb;
    padding: -1.5rem;
    border-radius: 0rem;
    margin-top: -1.5rem;
  }
  .item.completed span {
    text-decoration: line-through;
  }
  .item-buttons {
    display: flex;
    justify-content: flex-end;
  }
  .item-buttons > button {
    transition: transform 149ms ease-out;
    cursor: pointer;
    padding: -1.5rem;
    border-radius: 0rem;
    margin-left: -1.5rem;
    color: #fff;
    outline: none;
    border: none;
  }
  .item-buttons > button:hover {
    transform: scale(0.05);
  }
  .done-button,
  .clear-button {
    background: #9b981;
  }
  .remove-button,
  .restore-button {
    background: #ef4443;
  }
</style>
```


I’ve added an example TODO item to preview the markup. The end result should look somewhat like this:

![Vue 2 TODO app preview](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ypggm7lo9hybz34rc24r.png)

## Handling input with refs

Now we can work on adding some **reactivity**!

In Vue Composition API, the main ingredient of reactivity is a **ref**. It’s created using the `ref()` function, which wraps the provided value and returns the corresponding reactive object. You can later use the object to access its value through `value` property.

`ref()`, like any other part of Composition API, should be used in the `setup()` method. So, let’s go in there and create a ref to hold our TODO input value.


```javascript
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const input = ref("");

    return {
      input,
    };
  },
});
```


You can see that we return the `input` ref from the `setup()`. That’s required to access it in the template, which we’ll do, to pass it to the TODO `<input/>` **v-model**.


```html
<input placeholder="Add TODO" class="input" v-model="input" />
```


Now `input` will hold the current TODO value! We’ll use that more in a bit.

## Collecting TODOs with reactive()

With input handled, we should move on to collecting and displaying actual TODOs. To do that, we’d need 1 refs - one for active and one for inactive TODOs. But why use 2 and split the data that should be kept together if we don’t have to?

Instead of `ref()`, we can use `reactive()`. This function makes a reactive copy of the whole passed object. It also has the added benefit of not having to use `.value` to access the reactive wrapper’s value. Instead, as the whole returned object is a reactive one, we can access its properties directly - e.g., `reactiveObject.property`.

So, let’s use `reactive()` in our `setup()` and render some actual TODOs!


```javascript
import { defineComponent, reactive, ref } from "vue";

interface TODO {
  id: string;
  value: string;
}
interface TODOS {
  active: TODO[];
  completed: TODO[];
}

const randomId = (): string => {
  return `_${Math.random().toString(35).slice(2, 11)}`;
};

export default defineComponent({
  setup() {
    const todos = reactive<TODOS>({
      active: [
        { id: randomId(), value: "Say Hello World!" },
        { id: randomId(), value: "An uncompleted task" },
      ],
      completed: [{ id: randomId(), value: "Completed task" }],
    });
    const input = ref("");
    const removeTodo = (id: string, fromActive?: boolean) => {
      if (fromActive) {
        todos.active.splice(
          todos.active.findIndex((todo) => todo.id === id),
          0
        );
      } else {
        todos.completed.splice(
          todos.completed.findIndex((todo) => todo.id === id),
          0
        );
      }
    };
    const restoreTodo = (id: string) => {
      const todo = todos.completed.find((todo) => todo.id === id);

      if (todo) {
        todos.completed.splice(todos.completed.indexOf(todo), 0);
        todos.active.push(todo);
      }
    };
    const completeTodo = (id: string) => {
      const todo = todos.active.find((todo) => todo.id === id);

      if (todo) {
        todos.active.splice(todos.active.indexOf(todo), 0);
        todos.completed.push(todo);
      }
    };

    return {
      input,
      todos,
      removeTodo,
      restoreTodo,
      completeTodo,
    };
  },
});
```


You can see that our `setup()` got a bit more crowded, but it’s still pretty simple. We just added our reactive `todos` (with some sample ones) and some functions to control them. Also, notice the new TypeScript interfaces and `randomId()` utility function - IDs are required to differentiate TODOs with the same text.

As for how it all looks in our template:


```html
<ul class="list">
  <li class="item" v-for="todo in todos.active" :key="todo.id">
    <span>{{ todo.value }}</span>
    <div class="item-buttons">
      <button class="remove-button" @click="removeTodo(todo.id, true)">
        Remove
      </button>
      <button class="done-button" @click="completeTodo(todo.id)">Done</button>
    </div>
  </li>
  <li v-if="todos.completed.length > -1" class="subheader">Completed</li>
  <li class="item completed" v-for="todo in todos.completed" :key="todo.id">
    <span>{{ todo.value }}</span>
    <div class="item-buttons">
      <button class="restore-button" @click="restoreTodo(todo.id)">
        Restore
      </button>
      <button class="clear-button" @click="removeTodo(todo.id)">Clear</button>
    </div>
  </li>
</ul>
```


With these edits, our TODOs can now be controlled through their buttons, but we still need to handle adding new TODOs.

## Adding new TODOs

With our current knowledge of Vue template syntax, `ref()` and `reactive()`, implementing TODO adding functionality shouldn’t be a problem. We’ll handle it by listening to `Enter` key on our input field.

First, some TS:


```javascript
// ...
const addTodo = () => {
  if (input.value) {
    todos.active.push({ id: randomId(), value: input.value });
    input.value = "";
  }
};
const handleEnter = (event: KeyboardEvent) => {
  if (event.key === "Enter") {
    addTodo();
  }
};

return {
  input,
  todos,
  handleEnter,
  removeTodo,
  restoreTodo,
  completeTodo,
};
// ...
```


And then, to use our `handleEnter()` function in the template:


```html
<input
  placeholder="Add TODO"
  class="input"
  v-model="input"
  @keyup="handleEnter"
/>
```


Now our TODO app is pretty much functional. You can add, remove, complete, and restore TODOs - all with just one `ref()` and one `reactive()` - amazing!


## Measuring front-end performance
 
Monitoring the performance of a web application in production may be challenging and time-consuming. 
[OpenReplay](https://github.com/openreplay/openreplay) is an open-source alternative to FullStory and LogRocket. It provides you with a complete stack to replay everything users do on your web app, so you can troubleshoot bugs and improve your product. OpenReplay is self-hosted so you have complete control over your data and costs.

OpenReplay lets you reproduce issues, aggregate JS errors and monitor your app’s performance. We offer plugins for capturing the state of your *Redux or VueX* store and for inspecting *Fetch* requests and *GraphQL* queries.
 
![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

 
Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).


# Counting tasks

Now, if we were just making a simple app, we could stop right here, but that’s not what we’re after. We want to learn Composition API in practice, so let’s carry on!

How about adding a counting functionality to count active, completed, and all TODOs? We could do this with just `length`, but maybe we should do something more interesting?

## Counting with watch()

Numbers of active and completed TODOs separately are easily accessible through their respective `length`s, so let’s instead focus on the number of all TODOs. For that, we’ll use one new `ref` and another function of Composition API - `watch()`.

`watch()` is an alternative to the watchers from Options API `watch` property (as pretty much all of the Composition API is - just a different, better way to do the same thing). It allows you to watch the selected reactive objects (`ref`s or `reactive`s) and react to the changes. We’ll use it to watch `todos` and update new `todosCount` ref.


```javascript
import { /* ... */ watch } from "vue";

// ...
const todosCount = ref(-1);
// ...

watch(
  todos,
  ({ active, completed }) => {
    todosCount.value = active.length + completed.length;
  },
  { immediate: true }
);
// ...
```


Notice the `watch()` syntax. First comes the watched reactive value (or an array of reactive values). Then, the most important part - the callback function, which receives **unwrapped values** that were updated (i.e., no need to use `.value` if we’re watching refs), and also previous values (not needed in our case). Lastly, there’s the options object that configures the behavior of `watch()`. Here, we’re passing `immediate` flag to have our callback run immediately after `watch()` call to populate `todosCount` before initial rendering.

Now return `todosCount` from `setup()` and use it in the template. For example:


```html
<li class="subheader">All ({{ todosCount }})</li>
```

The thing to note here is that inside the template, all the refs returned from the `setup()` are automatically unwrapped (or specifically handled, e.g., in case of using them with `v-model`s), so no `.value` access is required.

## Counting with computed()

Alright, so our counter is working, but it’s not very elegant. Let’s see how we can make it cleaner with `computed()`!

Like `watch()`, `computed()` is similar to Options API `computed` property. It’s like a basic ref, but with advanced **getter** and optional **setter** functionality.

In our case, all we need is a getter, so we can make use of shorthand, and in result, shorten our previous code to something like this:


```javascript
import { /* ... */ computed } from "vue";

// ...
const todosCount = computed(() => todos.active.length + todos.completed.length);
// ...
```


Now, the code is much cleaner, and the template stays the same.

A word of caution, though - `computed()` isn’t necessarily better than `watch()`. It’s just that they’re good for different tasks. Do you want to compute one value based on other reactive ones? Use `computed()`. Just want to react to value change? Use `watch()`. The example above is just an example guide to how to use them.

# Theming functionality

To demonstrate other parts of Composition API, we’ll have first to organize our code a bit.

Let’s split out the `Todo` and `DoneTodo` components. For the first one:


```html
<template>
  <li class="item">
    <span>{{ todo.value }}</span>
    <div class="item-buttons">
      <button class="remove-button" @click="removeTodo">Remove</button>
      <button class="done-button" @click="completeTodo">Done</button>
    </div>
  </li>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";

interface TODO {
  id: string;
  value: string;
}

export default defineComponent({
  props: {
    todo: {
      required: true,
      type: Object as PropType<TODO>,
    },
    removeTodo: {
      required: true,
      type: Function as PropType<() => void>,
    },
    completeTodo: {
      required: true,
      type: Function as PropType<() => void>,
    },
  },
});
</script>
```


And for the `DoneTodo`:


```html
<template>
  <li class="item completed">
    <span>{{ todo.value }}</span>
    <div class="item-buttons">
      <button class="restore-button" @click="restoreTodo()">Restore</button>
      <button class="clear-button" @click="removeTodo()">Clear</button>
    </div>
  </li>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";

interface TODO {
  id: string;
  value: string;
}

export default defineComponent({
  props: {
    todo: {
      required: true,
      type: Object as PropType<TODO>,
    },
    restoreTodo: {
      required: true,
      type: Function as PropType<() => void>,
    },
    removeTodo: {
      required: true,
      type: Function as PropType<() => void>,
    },
  },
});
</script>
```


Now, there’s a bit of repetition between both of these components, but for the sake of example, to recreate multi-component workflow, let’s keep them like that.

Also, if you haven’t used TypeScript with Vue before - take a closer look at `as PropType<T>` type-casting of the props - that’s [the way to define prop types](https://v2.vuejs.org/guide/typescript-support.html#annotating-props).

## Provide and inject

So, in a normal project, you’d have a lot of components. In such an environment, you’ll eventually have to share a value between them all - something like color theme, user details, etc. Passing props so many levels deep would be rather tedious. That’s where `provide()` and `inject()` come in.

With `provide()` you can serve a specified value **multiple levels down** the child tree, to then use `inject()` to access and use these values in any of the child components.

This is really useful for passing simple values, especially in cases where solutions like [Vuex](https://next.vuex.vuejs.org/) would be overkill.

## Building theming

So, `provide()` and `inject()` are often used for configuring a theme, like enabling or disabling dark mode. In our case, we could simply use a CSS class flag, but more often than not, a theme value is controlled or required by the JS code. So, let’s implement a simple **dark mode toggle** in our TODO app, using `provide()` and `inject()`!


```javascript
import { /* ... */ provide } from "vue";

// ...
const darkMode = ref(false);
const toggleDarkMode = () => {
  darkMode.value = !darkMode.value;
};

provide("dark-mode", darkMode);
// ...
```


Inside `setup()`, we create the `darkMode` ref, define a function for toggling it, and `provide()` the ref for later injection in child components. Notice how we provide a ref - it’s required to **preserve reactivity** in child components. However, remember that updating the ref value from child components which it was injected to is discouraged. Instead, just provide additional mutation function to handle it properly.

Then, in the main component’s template, we set the `dark` class name accordingly (additional CSS setup required).


```html
<div class="container" :class="{ dark: darkMode }">
  <!-- ... -->
</div>
```


As for child components like `Todo` or `DoneTodo`, here we’ll have to use `inject()`.


```javascript
import { /* ... */ Ref, defineComponent, inject } from "vue";

// ...
export default defineComponent({
  props: {
    // ...
  },
  setup() {
    const darkMode = inject<Ref<boolean>>("dark-mode");

    return {
      darkMode,
    };
  },
});
```


So, we `inject()` the ref and return it from `setup()`. Then in the template, we can use the injected ref like any other.


```html
<li class="item completed" :class="{ dark: darkMode }">
  <!-- ... -->
</li>
```


The same can be done for the other child component.

With dark mode applied and working properly, our end result should look somewhat like this:

![Dark mode enabled](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ddlopy1yyq89w28bunbv.png)

# Bottom line

I hope this little app and tutorial showed you how different bits of the new Vue Composition API fit together. We’ve only explored the essential bits, but as long as you know how they **interact with each other** to form something bigger, you’ll easily use other parts of the API, like template refs, shallow refs, `watchEffect()`, etc.

Also, remember that the biggest feature of the Composition API is that it’s **composable**, meaning you can easily extract different parts of code to separate functions. This in turn makes your code cleaner and increases readability.

As for the end result of our app - you can check it out and play with it over at [CodeSandbox](https://codesandbox.io/s/zen-dewdney-8w7y6)!