1. Introduction to Pinnia and its Features
Hello, I'm Adam Jarr, co-founder of Vue Mastery. Today, we'll explore Pinnia, a lighter, more modular state management solution for Vue apps. We'll cover organizing stores, options vs. setup stores, accessing and updating state, and unique Pinnia features like patch and reset. We'll use a demo app, a Restaurant Finder, to illustrate these concepts. Let's dive in!
Hello, my name is Adam Jarr. I am co-founder of Vue Mastery, the ultimate learning resource for Vue developers. Through our collection of Vue courses, you can level up your skills, elevate your code and become the best Vue developer you possibly can be.
So in today's talk, we're going to take a journey deep into the Vue ecosystem to explore the planet of Pinnia. As we should all be aware by now, Pinnia is the evolution of where VueX was, bringing a newer, lighter weight, more modular, less prescriptive, more freeing version of state management into our Vue apps. And as the saying goes, with great freedom comes great responsibility. And we have added responsibility now because we can get creative about how we implement state management in our Vue apps. And when we do this well, we can use Pinnia to develop very elegantly architected apps, avoiding anti-patterns, and creating an application whose state can scale as we scale.
And that brings us to the focus of today's talk, where we unpack the proven Pinnia patterns that you can use confidently throughout your apps, because this talk and its contents were approved by Posva, or Eduardo San Martín Marrote, the creator of Pinnia. So by the end of this talk, we will have covered organizing our Pinnia stores, options versus setup stores, how they differ, which one you might want to choose for what use case, accessing and updating your Pinnia state. And we'll also explore some unique Pinnia features such as patch and reset. Throughout this talk, we're going to be looking at a demo app so we can apply and unpack these features in a more real-world use case. And as you can see, this is a Restaurant Finder. You're going to type in the city and search Sherm to find restaurants within a certain area, and the user can register an account. They can save their favorites, and they can read information pulled from the Google Maps API, such as ratings and reviews. This app is going to be tracking each of its logical concerns globally with its own Pinnia stores, and we're going to cover all this throughout the talk, so let's get started.
2. Why Use Pinnia and Its Features
Vue 3's composition API provides a basic state management solution, but Pinnia offers consistent patterns for collaborative organization, SSR security, DevTools for debugging, TypeScript support, and an intuitive developer experience. If you need these features, pinya is the way to go.
Before we dive deeper into Pinnia concepts, let's first get clear on why you would actually want to use Pinnia in the first place, because Vue 3 already has the composition API with a built-in reactivity system with the flexibility for sharing and reusing state. Just using the composition API, we could create a reactive object to serve as a store to manage our global state. Then we would just import that store into whichever component needs it, and because of how Vue's reactivity system works, any component that imports that store can directly mutate its global state. And if that state were to change and it had a template that were displaying that state, that template would reactively change as well.
3. Defining Pinya Stores and Their Differences
When using Vue 3 and pinya, you can choose to define stores as options or setup stores. The structure of an option store is similar to the Options API, while a setup store is more akin to the Composition API. Setup stores offer advantages like easy integration with composables and watchers. In a demo app, we used a setup store called geolocation that relied on the usegeolocation composable to fetch the user's coordinates and trigger actions to fetch location data from the Google Maps API.
When you're using Vue 3, you can choose to use the Options API or the Composition API. Similarly, when you're using pinya, you can define your stores as options or setup stores. Setup is going to refer to the setup function within the Composition API version of a pinya store. And there's really no right or wrong answer for which one you should be using in your apps, but let's explore the ways that they differ so that you can see which one might be better for your specific use case.
First, let's review how we define each type of pinya store. Anytime we want to create a store, whether it's an option store or a setup store, we must first import define store from pinya. Then we pass in a string for the name of the store as the first parameter. And this string needs to be unique because this is what pinya is going to be using to track the state of your store. And it's also what you're going to see in the dev tools. It's also going to be helpful when you start to use plugins in pinya. So just be very mindful to have a unique and nice understandable name for each of your stores as you're defining them.
Now, let's take a look at the structure of an option store. Not coincidentally, this kind of store takes in an object as the second parameter of the define store function. The state actions and getters here are really analogous to the data methods and computer properties when you're writing a component with the options API. Alternatively with a setup store, the second parameter that you're feeding into define store, that is going to be a function. This kind of store will feel familiar to how you'd compose a component with the composition API. We create state with ref or reactive. Our action is simply a function and our getter is a computed property. And note how we are returning everything here that we want to be accessible to other components. This is also gonna make everything here that is returned trackable by the dev tools, so just make sure you don't forget to do that.
Ultimately, like I mentioned earlier, whether you use options stores, setup stores or options end setup stores in your same application that is gonna be up to you and your team, but I did want to explore some of the ways that setup stores do have advantages. So instead of stores allow us to take advantage of the view3 reactivity system, that's gonna mean we can easily use composables in those stores, like composables from the view use library. And we can also use watchers easily in them as well. We actually use both of those in the demo app that I referenced in the beginning of the talk. So let's take a look at that. Notice this behavior here, where if the user doesn't type anything into the city input, their current location will be found automatically. Looking at the code inside of a store within this project that is called geolocation. We can see that it is indeed a set up store and its functionality relies on the usegeolocation composable from view use. It's using that to get the coordinates of our user. And then meanwhile, the watchers keeping an eye on those coordinates to trigger an action to fetch the location data from the Google Maps API.
4. PINIA Modularity and Store Organization
PINIA is modular by design, encouraging the splitting of state into manageable domains. Unlike Vue X, PINIA allows the creation of stores for each major logical concern, improving code splitting, type inferences, team collaboration, and debugging clarity.
So again, we're able to use both that composable and the watcher within this set up store. Now, it's important to understand that PINIA is modular and it's modular by design. That means that we are encouraged by its very API to split up our state into manageable domains. This compares to Vue X where we had one root store file and then modules that broke off from that. With PINIA, we create stores that are devoted to each major logical concern of our app and then we can import those stores wherever they're needed. And this is gonna help with bundlers for code splitting, it's gonna give us better type inferences if we're using TypeScript, it's gonna boost our team collaboration and it's gonna add clarity for debugging.
5. Store Organization and Nested Stores
When it comes to store organization, it's not always clear how to split up stores based on their concerns. In our example app, we separate the user authentication into one store, while in the restaurants app, we have separate stores for geolocation and restaurants. We can also use nested stores to access data from one store in another. The key takeaway is to group related data into separate stores based on logical concerns.
However, when it comes to actually putting this modularity into practice, it's not always so clear where and how you should be splitting up those stores based on the topic or the domain that they are concerned about. So that brings us to the topic of store organization or as I like to call it, store organization.
Let's take a look at our example app to see a straightforward kind of obvious way that we would separate out our stores and then we're gonna look at one that is a little more complex and nuanced. In the example app, our user can register an account and the user interface is gonna change depending on whether a user is logged in. So if they are, we're gonna see their name and the favorites showing up in the nav bar. So to achieve this authentication behavior, we need to keep track of the user state and also the actions related to that state in terms of registration, login and log out. So it's pretty obvious that this can all be bundled into one store and we might call that the auth or authentication store. But not every store is gonna be that intuitive with how you should be organizing the contents of it.
So let's look at a little less obvious way that we would be store organizing within our Pinia apps. Remember how in our restaurants app, we have these two inputs. The first input takes in the city, the second takes in the search term to find relevant local restaurants that are nearby that city. Both inputs have event listeners that trigger a function when the user types in text. Each function makes a call to the Google Maps API. Specifically the city input is gonna call for the latitude and longitude of the location typed in by the user. And then search is gonna make a call for the relevant restaurants within a certain distance of that city. In other words, both of the city and the search inputs are utilizing geolocation specifically with the Google Maps API. So should we combine this into one store, maybe we would call it Google Maps. Well, when we look at our project holistically, there are quite a few actions that are gonna rely on the Google Maps API. So that would be a lot of code that we're cramming in to one store. And more importantly, the actions aren't all gonna be related to the same kind of logical concerns. So to get clear on how this should all be storeganized, we can break it down to focus more on what is the Google Maps API actually being used for, and what state do we need to be tracking. When we ask those questions, we know that the first input is being used to make geolocation requests, while the second one is dealing with restaurants that match the search term. So yes, those restaurants are nearby, so there's an aspect of location involved with restaurants, but it's not its primary focus, like the other input is that's actually making those geolocation requests. So based on this reasoning, it sounds like we can make two separate stores, each with their shared logical concerns, geolocation and restaurants. But you might be thinking, well, in the restaurant store, when we request that list of relevant restaurants, it requires the use of geolocation data. So this means we need to use the data from the geolocation store inside of the restaurant store. Does that fact just completely ruin our approach here? Well, no, it actually doesn't because that brings us to another point within Pinia, which is called nested stores. With nested stores, we can have a store and then nest another store inside of it based upon what it's parent in this context would have needed. So we're making good progress in understanding some of the Pinia features. So some key takeaways so far to take note of are that we should be grouping related data into their own stores by logical concern.
6. Organizing Stores and Accessing State
We can organize our stores by features within our app and cross share state by nesting stores. Pinia allows flexibility in accessing state, with different approaches for option and setup stores. In an option store, state is accessed using 'this' in actions and by passing state to getters. In a setup store, state is accessed directly, like in the setup function. Remember to return state for accessibility outside the store.
We can also organize our stores by features within our app. We don't necessarily want to assume that a store should be created around one API or one library, and we can cross share state amongst and between stores by nesting those stores inside of each other.
Now that we covered all that, let's look at the topic of how do we access our state within Pinia. While other state management libraries can be very prescriptive and really force developers to be accessing and mutating their state in a specific way, this can lead to unnecessary overhead and some rigidity that can feel limiting. But one of the ways that Pinia provides flexibility is by allowing developers to make their own choices about the patterns that they're using. So let's take a look at how we can access state within Pinia and the options we have for doing that.
If we're in a store itself, we have access to the state properties inside of our actions and getters. But there's some considerations to keep in mind as things are a bit different between an options versus a set up store. In an option store, if we're in an action and we need to be accessing state, we can do so using this. Within a getter in an option store, we're going to need to pass in the state to that getter to be able to access it. Now, on the other hand, in a setup store for both actions and getters, we access the state property directly, just as we would within the setup function and the component that's using the composition API. In this example, the state property is a ref, so it's written as city.value. And of course, we have to remember to return everything so it can be accessible outside of the store.
7. Accessing and Mutating Pinia State
To access piniaState from inside a component, we can import the store and use the useStore function. Destructuring the properties from the store can make accessing and writing to the state easier. However, we need to be careful to maintain reactivity by using the storeToRefs helper. We can use vModel to bind input values to the pinia state.
Now, what about accessing piniaState from inside of a component? Now, there's going to be a few ways to do this, the most common of which is to import the store into the view component itself and then invoke the useStore function. This is going to allow us to read the state and write to that state using dot notation. However, using dot notation could become burdensome if we're using a lot of different state properties than a component.
So there's a more efficient way that we can actually do this. We're going to make our lives a lot easier by de-structuring the properties from the store so we're not having to write that store name all the time that we're accessing and writing and reading to it. But we do have to be careful about how we do this. Our first instinct might be to do something like this, but this is actually going to be breaking reactivity. In view three, we can't de-structure props unless we use a helper called toRefs. You might be already familiar with this. And in pinia, similarly, we can't de-structure state properties unless we use a pinia helper called storeToRefs, and this will keep the reactivity intact.
Now, we can make use of this de-structured state to push new data into it. Now let's take a look at how vModel would work if we're using that with a pinia store. We can use it to bind something like our input here to the pinia state so that our template stays in sync with the store and reacts accordingly. So those are some common ways to be accessing our pinia state. And some key takeaways that I want you to remember is that in an option store, we can use this. In an option store's getter, we pass in state. In a setup store, we access state properties directly. We don't use this. In a component, we can de-structure state properties with that StoreToRefs helper. And then we can use vModel to bind a value in our component to our store state.
8. Mutating Pinia State
We have different ways to mutate the state in Pinia. The most common way is to trigger an action in the store, but we can also change state directly or use the patch method to apply multiple changes at once. Pinia also provides a reset method to reset a store's state. Unfortunately, the reset method is not available with setup stores, but you can write your own setup function to achieve the same functionality. Another useful method is the onAction method, which provides detailed information about actions.
All right, so we looked at some ways to be accessing our pinia state. Now how do we go about mutating that state? As a refresher, when we look at how Vuex worked, when we mutated state, we would dispatch an action, that action would then go ahead and commit a mutation. And this was really the only recommended way to be mutating state unless you maybe were breaking that pattern against those recommended best practices. But of course, pinia is different. We no longer have that mutation step, and we have a lot more flexibility now with how we update our state.
The most common way to be mutating state within pinia is to trigger an action in that store, causing that state to be changed. In this example, clicking the Add to Favorites button triggers the Add to Favorites action in the Favorite Store. Now some people are surprised to learn that this is not the only way to change and mutate state within pinia. We can also change state directly by assigning a new value to the state property. In this example, we have a watcher on the city value, and then if the user deletes the city, we clear out those state properties directly so the user can start a new search.
Another way we can update state is to use the pinia patch method. This allows us to apply multiple changes at the same time to a store state. Here, notice how we're passing in an object with the updated state properties inside of it. Now, using patch comes with some added benefits because it's gonna create a single entry into the dev tools, and if we follow this convention throughout our app, we could easily do a search for patch to locate every place within our application where we're updating state in bulk like this. Another useful thing to note about patch is that it can take an object or a function as its parameter. So for example, if our state property was an array, we could use array methods to be updating our state accordingly.
Conveniently, pinia also offers a reset method. So we can reset a store's entire state to its initial value. In this example, the reset method is used within the store itself to clear out the state of our auth store. Notice how we are in an options store, and so we're using this. We can also use reset in a component like so. Using the reset method like this is super useful when we want to update an entire store at once, like when a user navigates to a certain page. For instance, let's look at this example in the router file where we use pinia's reset method in the router itself. So here we're saying whenever the user navigates back to the homepage, clear out that previous search. Now, unfortunately, the reset method is not available with setup stores, so this is one of the ways where the option store definitely has an advantage over the setup store. And if you're wondering why this is the case, it's because the reset method relies on the state function to create a fresh state, replacing the current store dot state with a new one. But we don't have access to the state function in a setup store. But if you still want that functionality where you reset an entire store, but it's a setup store, so you can't use a reset method, you could write your own setup function that achieves the same thing, where you write an action that resets the entire store, like so. When it comes to mutating our state, Pinea also offers a helpful method that we can use to get very detailed info about our actions. This is called the onAction method.
9. Pinea Features and Learning Resources
This method provides hooks to perform logic when an action happens. Pinea is a flexible way to architect and manage state. Explore the Proven Pinea Patterns course for more features and patterns, including plugins and custom behaviors. ViewMastery offers a full playlist of Pinea courses, covering fundamentals, Q&A with the creator, and an exclusive course on 5 Elegant Ways to Use Pinea. The platform also provides content on NUX, composables, utility-first CSS, and more. Team accounts receive a 55% discount, while individuals can choose monthly or annual plans. Grab the free Pinea cheat sheet for fast learning.
And as you can see, this method has a number of hooks we can use to perform some logic when a certain action happens. Notice how we can pass in state as a second argument in case we need it. Here in this example, it's being used to log information about which action has been triggered in the Auth store. To see this working, we can look inside the console, and we indeed see the name of the action, as well as the arguments that were passed into it.
So as you can see, Pinea is an evolved, flexible way we can architect and manage the state in our applications. If you wanna keep learning about the features and patterns within Pinea, I do invite you to take the Proven Pinea Patterns course over at ViewMastery.com. That's going to include everything you just saw in this talk plus more, where we explore plugins and how to extend the functionality of Pinea with your own custom behaviors.
And on our website, we have a full playlist of Pinea courses. So if you or your teammates need to start with the fundamentals, you can build a to-do app with us. We also have a Q&A with the creator of Pinea, Eduardo, so you can learn a bit about common questions that people have and his answers to them. We just released an exclusive course taught by Eduardo called 5 Elegant Ways to Use Pinea. So you can really get inside the mind of the person who wrote the library and use it like a pro.
And of course, being the ultimate learning platform for Vue developers means we have content about topics across the board. So if you're looking for NUX content, you can build a blog with the NUX content module. You can learn about NUX middleware, the NUX server, start to get into more full stack NUX concepts. If you're a fan of the composables from the Vue-Use library, you can take our Coding Better Composables course and learn how to create your own from scratch. You can learn how to use utility-first CSS with the popular Tailwind library, and we have exclusive content taught by the creator of Vue and Vite himself, Evan Yu. All these courses and more can be found in our comprehensive library of content that you can use to become the best Vue developer you possibly can be.
If you're part of a team that's using Vue, I invite you to join together under a team account, and when you do, you can get a 55% discount to access all of our content. Of course, if you're an individual, we have monthly and annual plans to have access to our content. And finally, if you want a free resource that's gonna help you learn Pinea fast, go grab the free Pinea cheat sheet at the URL on your screen.