Keep state easily using a composable in Vue 3
Often when starting a new project, people add a lot of dependencies to their project. When I ask them why they added a certain dependency I get an answer like: “that’s how it’s done” or “that’s how I’ve learned to do it”.
This often results in a lot of bloat in these projects while actually using a small part of the dependency. The same goes for Vuex/Pinia even though you don’t necessarily need it.
Let me start off with saying that Vuex is a very useful, cool and powerful tool and I’ve used it often in Vue 2 projects. Pinia in Vue 3 is even more powerful and useful in my opinion. So if you want to use it, you’re probably right to do so… But I’m here to show you an alternative way to manage states without having to use a state manager a.k.a. a store.
So why an alternative?
While building several projects I noticed that I used a bunch of variables that were kept in state. Even though they were only used in two components for example, they exist in the state manager. This means they are available in every component of the application even though they weren’t used there.
At some point I thought to myself: “I should be able to use only the parts that I actually need instead of having access to the entire state”.
Enter the composable
The composable is not a special thing that came with Vue 3 but rather a different way of building things. This method is more focused on separation of concerns and only using what you need, when you need it. You can compare it with a orchestra where you are the conductor. You then tell each musician when to enter the symphony with their instrument.
What does it do?
A composable is basically a function (inside its own file) that can be called from other parts of the application. It then returns an object containing variables, functions, etc.
I’m not going to go in too much detail about what composables are as there’s enough to find about this elsewhere, so let’s just focus on using it.
The setup
You probably have your own project that you’re currently working on, but for this example I’m using a basic Vue 3 project setup using the create Vue app. I will try to keep this example as simple as possible and use the least amount of different files so we can keep an overview on the actual changes.
Creating the composable
Now it’s time to actually create something that has use for this story. We’ll create a really basic composable consisting of a “global” state object, a “local” state object and a function that changes both objects so we can see the difference between the two.
In the main src
directory, I’ve created a directory called composables
where a file called stateful-composable.ts
is created.
import { reactive } from 'vue';
const globalState = reactive({
someString: 'Initial value',
someBoolean: false
});
export const useStatefulComposable = () => {
const localState = reactive({
someString: 'Initial value',
someBoolean: false
});
const updateValues = (stringValue: string, booleanValue: boolean) => {
// Set the global state values
globalState.someString = stringValue;
globalState.someBoolean = booleanValue;
// Set the local state values
localState.someString = stringValue;
localState.someBoolean = booleanValue;
};
return {
globalState,
localState,
updateValues
};
};
Please forgive the awful variable and parameter names, but this is purely to demonstrate what they are and keeping them short enough to fit in this code block 😅.
As you can see here, there is a function called useStatefulComposable
. This function is the one that will be called from other components in order to get the variables and function that are defined in the return
statement.
Above it is an object that will be our “global” state. This global state object will keep its values regardless of where it is called from so it will keep the same values across components.
Inside the function there is a similar object that represents our local state. This object will be independently initialized each time useStatefulComposable
was called and will not keep the same state per component.
Next we have a function called updateValues
that will update both the global and the local objects so we can demonstrate the differences.
Lastly a return statement is found that returns the two state objects and the function so we can use them in other components. We’ll do this next.
Importing the composable
Now that we have a composable we’ll start using it in our demo application. We will use the existing two routes to display the variables and see the difference between the two state objects.
Let’s first add the import to the homepage. In /src/views/HomeView.vue
, we’ll change the existing <script>
tag to look like the following.
<script setup lang="ts">
import { useStatefulComposable } from '@/composables/stateful-composable';
const { globalState, localState, updateValues } = useStatefulComposable();
</script>
What we’ve done here is importing and calling the useStatefulComposable
function. Then using object destructuring to split up the returned object into separated variables. This enables us to use the two state objects and function from the composable in our component.
In many cases you might use these state objects to perform additional checks or functionalities, but in this example we’re passing them directly to the template and use them there.
Adding the states to the template
Now we’ve imported the composable in our component and passed it to our template, let’s see it in action.
In the same file we’ll edit the <template>
in order to display the values of the two state objects. We’ll also add a button that will update the values using the function from the composable.
<template>
<main>
<div>
<h3>Global state</h3>
<p>String value: {{ globalState.someString }}</p>
<p>Boolean value: {{ globalState.someBoolean }}</p>
</div>
<br />
<hr />
<br />
<div>
<h3>Local state</h3>
<p>String value: {{ localState.someString }}</p>
<p>Boolean value: {{ localState.someBoolean }}</p>
</div>
<br />
<button type="button" @click="updateValues('Updated value through button', true)">Click me to update the values</button>
</main>
</template>
Nothing fancy to see here right? We’re basically just displaying the values inside the global and local objects and a button with a click handler that calls our function.
The result so far
This should result in the following on the homepage:
If you press the button the values of both the global state as the local state will change to “Updated value through button” and the boolean value will change to “true”.
So we’ve at least managed to create a state that has initial values and are updated by pressing the button. The state is being kept within the composable and also changed there, but the resulting state is usable in our component.
State between components
At this point in the story we have no difference between the global state and local state as they’re only used in one place. So let’s import and use this composable in another component to see the difference.
Importing the composable in a second component
Just as we did in the homepage, we’re importing the composable in /src/views/AboutView.vue
. By default it hasn’t got a script tag yet, so we’ll add it above the template.
<script setup lang="ts">
import { useStatefulComposable } from '@/composables/stateful-composable';
const { globalState, localState } = useStatefulComposable();
</script>
As you might have noticed, we’ve not included the updateValues
function from the composable, only the two state objects. This is done on purpose because I want to show the states between the pages but only change them in one of them.
Display the values in the template
For simplicity, we’ll copy paste the display of these values from the homepage to this page.
<template>
<div class="about">
<h1>This is an about page</h1>
<br />
<div>
<h3>Global state</h3>
<p>String value: {{ globalState.someString }}</p>
<p>Boolean value: {{ globalState.someBoolean }}</p>
</div>
<br />
<hr />
<br />
<div>
<h3>Local state</h3>
<p>String value: {{ localState.someString }}</p>
<p>Boolean value: {{ localState.someBoolean }}</p>
</div>
</div>
</template>
Remove style
To prevent a weird look in the about page, remove the entire <style>
-tag from the file.
Testing the states
We’re almost there. Let us see what we’ve created. First we’ll go back to the homepage. Maybe refresh the page to make sure we’re at the initial state. It will look like the following (the same image as we had before).
Now we have the initial state, we’ll press the button to set the state objects to the changed state.
After that, go to the about page using the top menu. As we’ve already changed the states we’ll see the global state retaining the changed values. But the local state is reinitialized and therefore showing the initial state.
Conclusion
That’s it! We’ve created a tiny bit of functionality that maintains it’s own state per page combined with a global state that keeps its values regardless of where it is imported.
This is a simple example but it is very powerful. A more real-world example could be a composable that retrieves data from an API and saves it in a global state. This can then be used throughout your components without doing more of the same call. Besides this, you could save data in the local state that only is used in a specific component or has to be reloaded every time.
Getting the code
You can get the result of this tutorial at my GitHub. This contains the exact same files and code as used here.