When we think about Vuex, Pinia, or stores in general we often think about state management and the Flux patterns but not only do stores not always follow the Flux pattern, there is so much more about stores that make them worth using! Plugins, Devtools, server-side rendering, TypeScript integrations... Let's dive into everything beyond state management with Pinia with practical examples about plugins and Devtools to get the most out of your stores.
Everything Beyond State Management in Stores with Pinia
AI Generated Video Summary
State management is not limited to complex applications and transitioning to a store offers significant benefits. Pinia is a centralized state management solution compatible with Vue 2 and Vue 3, providing advanced devtools support and extensibility with plugins. The core API of Pinia is similar to Vuex, but with a less verbose version of stores and powerful plugins. Pinia allows for easy state inspection, error handling, and testing. It is recommended to create one file per store for better organization and Pinia offers a more efficient performance compared to V-rex.
1. Introduction to State Management
Hi, I'm Eduardo, a frontend developer and a member of the Vue.js core team. Today, I want to discuss what is beyond state management and why it matters. State management is not limited to complex applications. It depends on how the store is used within your application. Transitioning from a basic reactive global state management solution to a store is a small step with significant benefits, such as server-side rendering and state outletting components. A centralized state shared by many components ensures scalability.
Hi, everyone. My name is Eduardo. I live in Paris. I'm a frontend developer and also a member of the Vue.js core team. I'm the author of its router and a small library called Pina, which is an alternative to Vue.js, a store library, as well, that I'm going to talk about today.
What I want to talk to you about is not only Pina but what is beyond state management, what matters in state management more than the state itself. But first, before even diving into Pina or anything else, what is state management? It's just having a global object with a few functions like that, global state. You have a function logging that allows you to change a state. Is this state management? And people will tell you that it is. And to be honest, it pretty much is, especially if you just add a reactive function call around that object. Well, you have a completely valid state, global state solution working for your view three or view two application.
And this is still state management and some people will tell you but state management is something you have and you create when you have bigger apps or medium to big apps or when you know your application is going to grow and is going to need that complexity in the future. But this is it is pretty much like, you don't need to have a complex application to need or consider using a state management solution or a store. That what defines what defines if you should or not use a state management solution is how the store is used inside of your application. For example, is your state used in many different places in pages or does your state out these pages? Do you need your state when you go from one page to another? Maybe your application only has three pages, but you require that, um, state to out leaves all the pages. And in that scenario you need a state management. And then the next question that comes to your mind, to to my mind is should I stop to, uh, should I stop having that bare bones reactive global state management solution or should I use a store? And the truth is there is a very small step to get to use a store. You just need using defined store with PNIA or UX5 and you can pretty much keep the same things that you were having. The state is going to be in a vertical state. Then you're going to have, uh, the global state variable is going to become these. So equals something. So the change is very simple. And the cost in terms of size, it also, uh, very small is like 1.5 kilobytes for PNIA. Um, but of course it comes with many other things, right? So do you really need a store? What makes you change your mind? What it means to go from, okay, I'm going to stop using my variable in solution and start using a store.
And there are a few reasons that will make you go that direction. The main reason is server side rendering, uh, is definitely so much easier to handle with the store like PNIA or Vuex, um, but there are many more, so you also have state outletting components. Uh, this is pretty much thumbs out of the box for the state management solutions as well. But, uh, depending on how and where you want to instance it, the global state is not something that always comes in. And then you have the centralized state shared by many components. So, if you create one single global state, that's not going to scale forever.
2. The Benefits of Using a Store like PNIA
If you keep adding things to your state, it will get bigger and more complex. Using a global object for state management lacks developer experience and makes it difficult to track changes. Testing and implementing additional functionality becomes more challenging. With a store like PNIA, these issues are resolved, and everything comes out of the box.
If you keep adding things, new functions, new properties to your state, at some point that state and that file is going to get bigger. You can split multiple files as well, but you're going to make your object grow in complexity way too much. And that's going to create all If you watch the state or do any other complicated stuff.
You also have a better developer experience. So, if you are using a global object, you cannot inspect, change that state within Dev Tools somewhere. You have to do it manually through the component inspector. You don't have anything in the timeline. You don't know where the changes to the store, to the states come from. If you have if you want to keep your state while you are changing the store or your state management solution, it's going to reload the page, you will have to handle hot mode replacement yourself. Then if you want to test or you need to test your components, you are going to need to mock your state management solution or test it twice.
So, have tests for your state management and also test that rely on the behavior of the store in the component. And then if you want to handle local storage or undo redo or any other functionality that is more generic and that applies to any store, any state management solution, you are going to have to implement that by yourself, whereas if you have a store, all these things come out of the box. You don't need to do anything. So this is why I want to talk to you a bit about how do we achieve these things, and what are these things that exist when you use something, a store like PNIA, and may you consider using one.
3. Introduction to PNIA
What is PNIA? PNIA is a registry of all the stores, the centralized state. When you create a PNIA, it has an empty reactive object as its state. When you call a uStore for the first time, that store is created and its state is dynamically added to the PNIA object. This centralized state concept has been a core part of PNIA since its inception, even before Vue 3 was released.
So first of all, what is PNIA? If you take these main.js file, .ts file that you have in your project, you will see that you have these app, create app, and to add PNIA you only need to import this create PNIA from PNIA and then create what we call a PNIA, and use it. And this is a very important part of using PNIA because that object that you have is the registry of all the stores, the centralized state. That means that when you create the PNIA there is a state property on that object that is pretty much an empty reactive object. And later on, whenever you call a uStore for the first time, that store is going to be created, and its state is going to get added dynamically to that empty object that we created. And the same happens with any other store that we call after that. Okay? So that PNIA state is going to grow as the application, as the user keeps using the application as you need. And having this centralized state has been a core concept since PNIA at the very beginning, which was about two years ago already. And that was even one month before Vue 3 even got released for the first time.
4. Introduction to PNIA Compatibility and Limitations
When PNIA was first introduced, it was only compatible with Vue 2.0. The development process involved hacking around the non-public API of the dev tools to display limitations and state information. Duplicating getters and actions for each store created a larger memory footprint and worse performance. Additionally, there was no extensibility tool or plugin interface, only an option-based API.
So it means that when PNIA existed, the first version of PNIA, was only compatible with Vue 2.0. At that time things were very different from now. I was calling internal functions of the dev tools, V5. So that was pretty much just hacking around the non-public API of the dev tools to display limitations and the state in the dev tools where VueX was supposed to display its information. I would have to duplicate getters and actions every time we used store. That would create a larger memory footprint and worse performance when it comes to computer properties. There was no extensibility tool whatsoever. There was no plugin interface at all. There was only an option-based API.
5. Support for Vue 2 and Vue 3 with Pinia
Today, with Pinia Vue 2.0, we have support for Vue 2 and Vue 3. We have two different syntaxes - option API and setup syntax. Advanced devtools support, timeline inspection per component, modification of stores, hot module replacement, testing module, and type safe extensibility with plugins.
Today, with Pinia Vue 2.0, we have support for Vue 2 and Vue 3. We have two different syntaxes we can use. We have the option API and we have the setup syntax, which you're going to see later. We have a very advanced devtools support for the version six. We have a timeline we can inspect per component. We can inspect all the stores. We can modify them. We have hot mode replacement with a very simple one-liner for your stores. We have a testing module that allows you to pretty much create a testing pinia that locks any store that is used by your components without you having to change anything in your components. You have a next module that works for next two and next three. And you have type safe extensibility with plugins, which I won't dive into, but basically you can add any option to your stores, and then add properties to the store, and they can be typed safely. And you can use the ID of the store, the state the actions, anything you want. It's pretty powerful.
6. Introduction to Pina's Core API and Plugins
The core API of Pina is the same as UX5, making it easy to switch between the two. UX5 and Pina no longer have mutations, offering a less verbose version of stores. State can be directly mutated on the store or modified using the patch function. Pina also provides one-liner Homebody replacement and powerful plugins that allow for customization and merging of properties into stores.
And another thing about Pina, at least, that is important to mention is that the core API, the way you define the stores, is the same as UX5. So UX5 came after Pina, and it has influenced a lot the process and the API of UX5, and I keep a very close eye because I participated a lot in this discussion. So I make sure that anything that gets added to UX5, gets added to Pina in a way that it's very easy in the future to change from one to the other, especially for the core of it.
And the main changes of UX5 and Pina compared to UX4, and the thing that most people will notice is that we no longer have mutations. Overall, we have a much less verbose version of the stores. The mutations completely disappear. So you have instead the state and then you can mutate the state directly on the store, but that's not the only way, at least for Pina to modify the state. We can also use a function, patch, that allows us to pass partial object, a patch object that we applied to the store. And we can also pass a function so we can modify the state directly. This is useful when we are interacting with collections like sets, maps, or arrays.
Then we have the one-liner Homebody replacement, as I was saying before, and which is pretty much just a line you copy every time on the store and you forget about it. And then you have the plugins. The plugins in Pimia, I try to make them to give them as much power as possible. As a matter of fact, the dev tools implementation for Pimia is a Pimia plugin. You can do many things. So the plugins for Pimia are just functions and they receive the application, the Pimia instance, the store and its options, the options with which the store was defined. And usually you do three different possible things. You can either call a subscribe function that is going to trigger every time you mutate the state of your store, but it also accounts for patches. So if you do a patch with a function or with an object, it's only going to trigger one differently from a watch. Then we have an action, which triggers before an action executes and allows you to control the outcome. And if there is an error or if it succeeds, you can do other things. And then the most simple but yet so powerful functionality is to return an object on the plugins. These objects that you return in the plugins are going to get merged into the store. See, so this allows you to override or add new properties to every single store. You can also filter by ID if you want. You can also read from the options. You can do many things. And this is a very simple example is that you can just add the router to every single store and then in the actions, you can just call these.router. And that's it. You get the router, you can push, you can replace, you can check the current route if you want.
7. The onAction Feature and Error Handling
The onAction is a powerful feature used by the DevTools to display the timeline. It can also be used to send errors from actions to a reporting service like Sentry. By calling an action and using onError, you can retrieve the error and set the errors with the name of the failed action, along with additional information such as the storeID and the arguments passed.
But one of my favorite ones is the onAction. The onAction looks very simple and it is very simple for a lot of cases, but it's very powerful. So this is what is used by the DevTools behind the scenes to display the timeline which we're going to see just in a moment. But for example, this is the code you need to send errors from your actions to our reporting service like Sentry or anything. So you just call an action, and then onError, you get the error and you set the errors with the name of the action that failed. You can also send the storeID, which I forgot to put there, and then the arguments that were passed.
8. Using PINIA and Demonstrating Functionality
I want to show you more functionality like the next plugin and the PINIA testing module. Let me show you. I have a server running with my application. I have a simple page with a counter demo. I can increment the state and add new properties. The dev tools allow me to inspect and debug the counter store, with a timeline showing changes.
And I want to show you, there is much more like the next plugin and the PINIA testing module that I want to show you, but I wanted to give you a taste of using PINIA, what it looks like with all these functionality. So let me show you.
So I have a server running. All right. So I have a server running here with my application. And I have one page, a very simple page, where I have counter demo. This is just as you can see. So we have this up and running. The demo counter, I have a user counter that I'm importing from this file right here. And I'm just using the counter and I'm directly calling counter. n++ and I'm displaying the whole state of the counter.
And so, the demo counter, what I have is just a defined store. I have my initial state here. And I have the hot module replacement snippet of code. And what I want to show you is here I can increment the state, but I can also add new properties. OK? And if I add a new property, it will appear. It will leave the old state, but it will add itself to the state of the store. I need to remove it because it's going to disappear from the state as well. So of course, this works with everything else, the getters, the actions. And again, I want to show you a little bit of the dev tools that we have and how cool it is to work with a store and debug your applications, thanks to the dev tools. So here I have an increment, I can increment my state. And if I open the dev tools here, I can inspect the counter, and I can see here that I have my state. I can increment it. I can change it if I want. And here I have a timeline. And if I go down, I can see that I have a pina. And if I increment, I can see small dots, yellow dots. And if I select them, let it go, we can see three different dots that we have, which are here. And what I'm doing. So here, rotation goes from 102 to 103 on the key N, the key of the counter.
9. Inspecting Actions and Mutations
I'm going to have an action that is going to decrement the previous until it gets to zero. This is going to be an async action. We can inspect the actions and inspect all the mutations by group. We can also go to the state management and get to the actual state, modify it while the action is happening. If we want to group multiple modifications, let's say we want to count how many times we decrease time, how many times we decrease n.
So what I'm going to do, I'm going to have an action here. I'm going to have an action that is going to decrement the previous until it gets to zero. This is going to be an async action. And I'm going to do while this dot N is bigger than this zero, I'm going to decrement, and then I'm going to await a small delay of time of 200 milliseconds.
So that's it, that is the action, it's very simple. Now what I'm going to do is at a button here I'm going to call counter dot decrease to zero. And we have auto-completion as well, pretty cool. There we are, decrease to zero. We'll just change the text a little bit. Alright so here we are just doing 200 because it's faster than waiting one second. What we want to see is how the changes of n can be inspected one by one in the deft tools.
So here, I'll just show you the timeline, we've gone clear, and again to decrease to zero. And as you can see here, they start appearing, you can see how n started decreasing and what's interesting, I can also call increment, and they appear right under and I can also have multiple actions running in parallel as you can see until n gets to zero. And then we can inspect the actions and inspect all the mutations by group. So here, if I switch to group, I can see that this action here, we have a total of 74 events and it took 40 seconds, 14 seconds sorry, to finish. The other ones are a bit shorter, so we have less events and they took less time. And every time we can see the new value and the old value. So that's pretty cool. And what's interesting is we can also go to the state management here, get to the actual state so we can see all the states, we can change them from there. We can do that while the action is happening, we can modify that anytime. So here if I modify the value and I go to 50, you can see that it changed right away here. We don't need to do anything else. And in the timeline, we have the events happening at the same time when they get logged in. Furthermore, if we want to group multiple modifications, let's say we want to count how many times we decrease time, how many times we decrease n. So this means that decrease time increments, so it appears here. We can increment a few times, we decrease to zero. And now if I check the depth tools, I can see that I have more mutations. I have one for changing n here and one for changing decrease times, which has a type by the way. Let me see time. There you go.
10. Using Composable Functions in State
Inside of state, we can call any composable function that we have, such as watch, computed, or wrap. We can even save the state to local storage using the useLocalStorage function. This makes the state management simple yet powerful, allowing for the use of any composition API.
So one thing I can do instead is using the platform component. So we'll do these as state... Oops. And then here we can do what we want. And this will route the modification. So here let's do it a few times and deplement. Now we go check this and we can see that we have less events. We can inspect and see the old values and the new values. 5 and 0 for n. That was the last one.
Okay. The first one looks like 5 and 4. There you go. And one thing that is very interesting and a lot of people don't know, is that inside of state we can call any composable function that we have. We can call watch, we can call computed, wrap, anything we want. So if I want to save the state to the local storage, I can just use the use for you call function use local storage. And I can just replace use local storage counter the demo. And I can give it an initial value of zero. So I can use that. I'm going to reload the page because I just change the nature of this state property so I need to probably reload the page. We have a zero, if we implement here and I reload the page, we can see that the eight is the same. It hasn't changed. And everything else works the same. So very simple yet so powerful. You can use any composition API right from here and anything else works the same. Right. It's just this state so you can still inspect it here. You can still change it and it will adapt. All right. So this is the option version of a store.
11. Using the Setup Syntax and Exploring Functionality
And so you have a state, actions, getters, and options like the bounce option. The setup syntax uses a function instead of an object, allowing for more flexibility. You can use composable functions, such as the use cache request, and even add watchers. Navigating between pages shows immediate image loading and access to new stores. Errors can also be seen in the timeline and inspected in the dev tools.
And so you have a state, you have the actions, you have the getters as well. And you can add other properties to the options if you want. For example, you can have the bounce option that allows you to define which actions you want to bounce. But there is another version of the syntax that we can use. And that's the setup syntax.
So the setup syntax, I have a different example for that. It's a bit longer. I cannot go through the whole thing. But instead of having an object, like we have here, we have a function. This function is the same as the setup function on a component. And you just create the same as in a component, rest, computed, functions. You use your composable the way you want. For example, this is a use cache request. It's similar to a stale what we evaluate. We cache the first time, and it still does the request in the background. So a very, very simple one. And then we have a few functions. And you can even do watchers. So that's pretty cool, too.
Here, for example, if I navigate a few times, I get a few images. And then when I go back, you can see that I get the image right away. And in the background, it's doing a call over the network anyway. And here you can see that this store appeared. We still have a demo counter, which hasn't changed. But as soon as we got into this page, we got access to this new store. As you can see here, we can see how it changes from one day to the other. We can also have the errors, by the way, which I didn't show. But I don't think I have it implemented here to show you. We can see the errors in the timeline also. And we can inspect them right away in the dev tools, which is pretty cool.
12. Logging Errors and Poll Results
We can log errors through the dev tools, eliminating the need for console logs. This gives us access to the actual error source. Thank you to my sponsors on Github for supporting my open-source work. Consider supporting me if you like what I do. Let's look at the poll results. The correct answer was the least voted one, a fun fact about the pineapple fruit. Pineapple is a multi-fruit, with multiple flowers and fruits that fuse together, similar to how stores are created individually and gathered into a centralized store.
We can log them as well. So we don't even need to have console logs anywhere, because we can do that on demand through the dev tools. So you can focus on adding just debugger or breakpoints once you want to debug that. But you can just console log from the dev tools anything you want.
Usually we have, just to show you. Even though here it's not very important, but here you can log it to the console. And so that gives you access to the actual error where it comes from.
So that's it. That's everything I wanted to show you for the demo. I want to thank you to the sponsors I have on Github. Sponsors who allow me to work most of the time on an open source. So if you like what I do on the Voxer, on the UX system, on Kinea, or whatever, please consider supporting me, especially if you're a company. There are experts, by the way, on doing that. And I also want to thank you for listening to me. I hope you learned something and that you're going to give Kinea a try soon. Let's look at the results from the poll.
Since there was a technically correct answer to this, is that right? Yeah. Yeah, there is one correct answer. And which one was the correct answer? So the correct answer is the least voted one. It's a fun fact about fruits, more specifically the pineapple fruit. The other ones were a trap. The manga was clearly a big trap. I don't even know any pineapple cancer manga, but I thought that was funny. I wanted to vote for that just because it sounded fun.
Okay, so what's the fun fact? So it's written in the docs, pineapple is actually a multi-fruit. So it flourish as multiple flowers, and then you have multiple fruits going out of the flowers. And then they fusion together to become one big fruit, which is the pineapple. So this is pretty much like stores. You create stores individually, and at the end they gather together into one big centralized store, which is my pina, basically. Very cool, okay.
Global Store Usage and File Organization
The pattern for global store usage in Pina involves creating a store for each feature. This allows for the storage of information that outlives pages or requires some kind of persistence. It's also useful for putting business logic in a store, especially for features that are used across the application. With Pina, it's recommended to create one file per store to maintain organization. You have the freedom to import and use the stores as needed, as long as they are used within the setup functions or passed the Binia instance.
I guess it's time to ask questions. The first question comes from Organized Chaos. What is the pattern for global store usage? And also does Pina suggest splitting out stores for different component tracks like Vuex? We're assuming component tracks here means just related, feature related components. If we're wrong about that, Organized Chaos, sorry. I'm trying to find a question written so I can read it. Okay, there we go. Wait, I cannot find it. So, there is not really... You split, you create a store for a feature. So, as I say in the talk, you're usually putting the store information that outlives pages or that requires some kind of... Always just use an OAuth2 application so it doesn't have necessarily to outleave a page. Sometimes it's also easier to put the business logic in a store so that could be a specific feature. But especially with a composition API, that's really not necessary. So, sometimes, for some specific business logic, it might be useful especially if it's used across the application like authentication or in some e-commerce websites that the cart can get very complicated. So, ideally with Binia, you create one file per store so you don't create multiple stores in one file. That's it. There is nothing else. Then you're just free to do whatever you want, to import them, whatever you want, as long as they are used inside the setup functions or you pass the Binia instance as shown in the documentation.
Performance Comparison: Pinia vs V-rex
When updating the state of components in real-time, V-rex may be slow. Pinia's performance compared to V-rex is uncertain, as it may depend on the specific scenario. The slowness could be related to animated properties or watchers, but more information is needed to determine the cause.
Okay, next question comes from V-rex. When updating the state of tens to hundreds of components in real time as a reaction to a user input or for animation, we noticed V-rex is extremely slow. How is the performance of Pinia compared to V-rex? So I think the person is talking about V-rex 4 or V-rex 3, probably. I don't think V-rex has anything to deal with the slowness in this case of meeting these scenarios. So Pinia shouldn't change that. And maybe Pinia is faster than V-rex 4 and V-rex 5 will be faster than V-rex 4 as well. But I don't think that the animation that is slow. Yeah, it's probably not about data. It's more about the properties animated and maybe some watchers are seeing. It's just impossible to know without that, actually.
Directly Changing State in Vuex
You can change state directly in Vuex without using mutations, but this approach has limitations. By bypassing mutations, you won't get the DevTools experience and won't be able to see mutations or anything in the DevTools. This goes against the recommended way of using Vuex and can potentially break things, such as plugins not working properly.
Okay, next question. Comes from Ricky Yu. I can change state directly in Vuex already. I just bypass mutations. What's wrong with that, besides everyone telling me it's wrong to do? You won't get the DevTools experience. You are not able to see the mutations or anything in the DevTools. So that's a big impact on Vuex. That's it. Okay so, really we could just be doing that. I mean things could go wrong also. It has been two years since I haven't used a classic Vuex, a mutation-based Vuex. So I don't remember if there are other things that could break. Probably plugins won't work, like you have other plugins that won't react so it's just it's going against the way of doing things. So things are going to break. That makes sense.
Comparison of PNIA and Vuex5
Summarize PNIA versus Vuex5 top three reasons to migrate: plugin API, advanced DevTools experience, and TypeScript support. PNIA is only 1.5 kilobytes due to its simplicity and focus on development experience. Thank you for joining us, Eduardo.
Okay also from Ricky Yu, summarize PNIA versus Vuex5 top three reasons to migrate. So give us your elevator pitch. So I don't think Vuex5 is there yet anyway so there is nothing to compare to. There is an experimental branch. So imagine that there was one and that it has implemented the API that is in the RFC. So I'm pretty sure Kia has already a working version, we've seen it together. But things that differentiate today PNIA from Vuex5 is the plugin API which is existing and more advanced. The DevTools experience which is also more advanced as well. I'm not sure what DevTools experience exists for Vuex5. The testing module, so there is an official testing module for PNIA, I actually also did the Vuex version. And there is a Nuxt plugin as well. And it has a cute little logo, it has a logo. You know, that's a huge selling point I will say. Now the real reason is plugins and DevTools I would say. And if you want a third, that would be the TypeScript support. Wait, do you actually have the same TypeScript support? What was the other thing? The testing I would say.
Okay, next question comes from Mr. Backend. How is PNIA only one kilobyte? Because it's very simple. Actually, the biggest part is the DevTools. It's 1.5 right now. I think. Oh, yeah. Let's clarify. It's 1.5. Yeah, technically, it's not one anymore, right? It used to be one. But as I kept adding features like the plugins, and both the setup and the option syntax, it's now 1.5. Still very, very small anyway. Uh, you don't... It doesn't do that much. What can I say? Because everything is dev... There is a big focus on development as well, experience. So everything that is the development experience gets stripped out in production. Great, very cool.
Well, uh, it is about time for us. So thank you so much for joining us, Eduardo.