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.
// 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).