Your First Vue 3 Composable

The composition pattern (composable) is one of the most interesting features introduced by Vue 3 Composition API. It allows us to use reactivity outside of components in a straightforward manner, something unthinkable before.


The composition pattern (composable) is one of the most interesting features introduced by the Vue 3 Composition API. It allows us to use reactivity outside of components in a straightforward manner, something unthinkable until now.

This way, we can compose small logical pieces and reuse them throughout your project (or other projects).

This is the "official" definition of composable taken from the Vue documentation.

A function that uses the Composition API to encapsulate and reuse reactive logic that maintains its state.

The best part is that you probably don't have to learn anything new to use it, as if you understand how the Composition API works, you already know how to use a composable.

Now, how do you take the first steps with this pattern? In my experience, I've found that the best way is to use familiar scenarios as a testing ground.

A familiar scenario

I'm sure you'll agree: one of the most common scenarios in web development (with Vue, of course) is making an HTTP request, right? Something like that.

// AppComponent.vue
<script setup>
    import { ref } from 'vue';
    const results = ref(null);
    const API_URL = 'https://randomuser.me/api/?results=50';
    try {
      results.value = await (await fetch(API_URL)).json();
    } catch (err) {
      throw new Error(err.message);
    }
</script>

In reality, there's nothing too interesting in the snippet. We use Async/Await to request a list of users and store the response in a reactive reference using ref. Similarly, if something goes wrong, we capture the issue within the catch block and store the error in another reactive reference.

Your First Composable

How can we create a composable from this asynchronous scenario? The first thing is to choose a name. Think about the functionality or logic you want to abstract. In this case, it could be async or fetch, for example.

Let's use the first one to create a composable called useAsync.

We're going to create a composable that manages the entire lifecycle of an HTTP request asynchronously for us. Moreover, in a reactive way.

As you can see, the convention is to prefix the name of your composables with the word use, reinforcing the practical nature of the pattern itself.

Input and Output

The second step is to think in terms of input and output (IN & OUT), meaning, what information/parameters (IN) does this compositional function need to do its job? In the case of useAsync, I think just the URL with the endpoint where the request should be made is enough.

Now, what do we need this composable to return (OUT) so that we can use it and fulfill its mission? In this case, the result of the request and an error, if any: result and error. Both will be reactive references because, remember, * for the first time, we can use Vue's reactivity* (in a mainstream way) outside of components.

With a clear understanding of what our composable needs and what it returns, let's create it.

JS
// composables/useAsync.js
import { ref, readonly } from 'vue';
export function useAsync() {
  const results = ref(null);
  const error = ref(null);
  const makeRequest = async (API_URL) => {
    try {
      const request = await (await fetch(API_URL)).json();
      results.value = request;
    } catch (err) {
      console.log(err);
      error.value = err;
    }
  };
  return {
    makeRequest,
    results: readonly(results),
    error,
  };
}

Using a Composable

The code aligns with the definition I showed you at the beginning of this DevTip: a function that uses Vue's reactivity. What we're doing is exposing the useAsync function, which, in turn, returns a function that makes HTTP calls and accepts an API_URL. We also return reactive references with the result and possible error of the request (result and error).

Now we can make requests from any component (or even another composable), and we'll be reusing the same logic instead of duplicating it in different parts of the project.

// AppComponent.vue
<script setup>
    import { useAsync } from '../composables/useAsync';
    const { makeRequest, results, error } = useAsync();
    await makeRequest('https://randomuser.me/api/?results=50');
</script>

First, we import our composable useAsync. Then, we execute the returned function to access makeRequest, results, and error.

Now all that's left is to make the same HTTP request to the endpoint. Everything else should work exactly as before.

Considering that error is also a reactive reference, if we want to react to possible issues, we just need to observe it.

// AppComponent.vue
<script setup>
    import { watchEffect } from 'vue';
    ...
    
    watchEffect(() => {
      if (error.value) alert('Problem found: ' + error.value.message);
    });
</script>

Conclusion

The composable pattern is here to stay, so my recommendation is to start learning how to use it now. In this DevTip, we've only seen the essentials, but later on, we'll delve into other aspects such as state management (stateful vs stateless).

Demo

Juan Andrés Núñez
Juan Andrés Núñez
Frontend Engineer. Vue.js Specialist. Professional Teacher. Stoic.