VueT2 Light_1200x303

The next version of Vue is around the corner and we can already try some new features, like Vue Composition API, which is heavily inspired by React Hooks. A lot of developers are excited about it, others are not so sure. Let’s see how to use it and what the big deal is.

Just recently the Vue core team released pre-alpha version of the next Vue version – Vue 3. It is faster than the current Vue 2 and will also offer new and exciting features. One of those features is Vue Composition API.

The Composition API is heavily inspired by React Hooks. As a developer who works with both React and Vue on a daily basis, I could not be happier about this feature. It will allow us to create reusable and stateful business logic and make it easier to organize related code. What is more, it is free of caveats that exist in React Hooks. For instance, in React, Hooks cannot be called conditionally and are called on every render. You can read more about the differences here. Anyway, what is the big deal with this Composition API?

Vue is very easy to use and has a great API that is beginner-friendly and simple to understand. However, when components get bigger and bigger, it is much harder to maintain and understand them as different pieces of business logic are mixed up together. At the moment, there are a few ways of handling this, e.g., mixins, higher order components (HOCs), and scoped slots, but each of them has their own disadvantages.

For instance, HOCs is a pattern derived from React in which one component is wrapped with another component that spreads reusable methods/state values into the former. However, this pattern does not really play well with Single File Components, and I have not seen many developers adopt HOCs in Vue.

Mixins on the other hand are quite simple as they will merge object properties like data, methods, computed, etc., into a component via mixins property. Unfortunately, when there are more and more mixins, there is a higher chance for naming collisions. In addition, it is not so obvious where certain methods and state are coming from. It might require scanning through all the mixins to find out a particular method definition.

I am a fan of neither mixins nor HOCs, and when needed I would always choose scoped slots. However, scoped slots are also not a silver bullet, as you might end up with a lot of them at some point and basically more and more components are created just for the sake of providing a way for creation of reusable stateful logic.

Next, let’s have a look at the Composition API and how it works. For that we will create a new project and try it out!

Getting Started

Scaffold a new project with Vue-Cli. You can follow installation instructions from the documentation. We will use a package called @vue/composition-api as it will allow us to try the new API. After setting up the project and installing the required library open ‘main.js’ file and add these lines so we can use new features.

import VueCompositionApi from '@vue/composition-api';
Vue.use(VueCompositionApi);

Let’s start with a simple example. Create a new component called Count.vue. It will only have a button, counter, computed property, and a method to increment the count. Simple, but it shows how the crucial pieces of Vue components, namely ‘data’ state, computed properties, and methods, can be created.

<template>
  <div class="count">
    <button @click="increment">Count is: {{state.count}}, double is: {{state.double}}</button>
  </div>
</template>

<script>
import { reactive, computed } from "@vue/composition-api";

export default {
  name: "Count",

  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    });

    const increment = () => state.count++;

    return {
      state,
      increment
    };
  }
};
</script>

We have a new property called setup. This is where we can use functions for creating state, computed properties, etc. The setup method should return an object which will include anything that should be available in our component.

We imported two functions – reactive and computed. You can probably guess what these mean. Reactive is actually an equivalent of Vue.observable, which is available in Vue 2, while computed doesn’t really need any additional explanation. It is just a different way of directly creating a computed value.

Now you can import the Count.vue component in the App.vue and try it out in your browser.

<template>
  <div id="app">
    <Count />
  </div>
</template>

<script>
import Count from "@/components/Count";
export default {
  name: "app",
  components: { Count }
};
</script>

This is a simple example, but let’s try to do something fancier that you could potentially use in a project. Let’s create a function called useApi that will have a state for data, api_status, and initFetch function. It will accept a url and options object. We will use it to fetch a random dog from dog.ceo API. Create a new component called Dog.vue and between <script> tags add this code:

import { reactive, computed, toRefs } from "@vue/composition-api";

const useApi = (url, options = {}) => {
  const state = reactive({
    data: null,
    api_status: ""
  });

  const initFetch = async () => {
    try {
      state.api_status = "FETCHING";
      const response = await fetch(url);
      const data = await response.json();
      state.data = data.message;
      state.api_status = "FETCHING_SUCCESS";
    } catch (error) {
      state.api_status = "FETCHING_ERROR";
    }
  };

  if (options.hasOwnProperty("fetchImmediately") && options.fetchImmediately) {
    initFetch();
  }

  return {
    ...toRefs(state),
    initFetch
  };
};

This time, besides reactive and computed we are also importing toRefs. I will explain why we need it in a moment. In the useApi functions we declared state constant which has reactive data and api_status. Further, we have the initFetch function that will update api_status as well as fetch data for a url that was provided as an argument.

Next, we check if the options object has the fetchImmediately property. It will be used to indicate if an API call should be initialized when a component is created. Finally, we return an object with spread state values and the initFetch function. As you can see, we do not spread the state directly, but instead we spread a result of toRefs functions. The reason behind it is that when values returned from the state would be destructured, they would not be reactive anymore. Therefore, toRefs wraps each value in a ref thanks to which state values will cause a Vue component to re-render as it should when state values are changed.

The useApi function is now ready to be used, so let’s set up the rest of the component.

export default {
  setup() {
    const { data, api_status, initFetch } = useApi(
      "https://dog.ceo/api/breeds/image/random",
      {
        fetchImmediately: true
      }
    );

    return {
      dogImage: data,
      api_status,
      fetchDog: initFetch
    };
  }
};

As I mentioned before, we can destructure properties that we need from the useApi without losing reactivity. In addition, the object that is returned from the setup has renamed properties to better indicate what they are for. Now, the last thing to add is the template.

<template>
  <div style="margin-top: 20px;">
    <div v-if="api_status === 'FETCHING'">Fetching</div>
    <div v-else-if="api_status === 'FETCHING_ERROR'">Error</div>
    <div v-else-if="api_status === 'FETCHING_SUCCESS'">
      <img :src="dogImage" style="display: block; max-width: 500px; height: auto; margin: 0 auto;" />
    </div>
    <div v-else>Oops, no dog found</div>
    <button style="margin-top: 20px;" @click.prevent="fetchDog">Fetch dog</button>
  </div>
</template>

The template consists of a few divs which are rendered conditionally depending on the api_status. Due to passing fetchImmediately: true to the useApi, a random dog will be fetched at the start, and you can fetch a different one by clicking the Fetch dog button. It will initialize the fetchDog function which basically is the initFetch function returned from the useApi function. The last step is to import the Dog.vue component in App.vue and render it.

That is all we need. You now have a reusable function for fetching data that can be reused across your components. If you wish you can get more creative and improve it further. If you would like to know more about the Vue Composition API, then definitely have a look at the documentation. You can find the code in my GitHub repository.


Thomas Findlay
About the Author

Thomas Findlay

Thomas Findlay is a web and mobile developer, mentor, technical writer and consultant with almost six years of experience. He specializes in frontend web technologies including Vue.js, React.js, React Native for mobile applications, along with backend: PHP, Laravel, Python, Flask, Node.js and Express.js. Thomas has designed and developed websites and mobile applications for individuals and small and large businesses. Learn more about Thomas at https://www.codementor.io/thomas478

Related Posts

Comments

Comments are disabled in preview mode.