TypeScript and Vue 3 Global State Management with Pinia
Transcript from the "Global State Management with Pinia" Lesson
>> There's an important topic that we haven't talked about yet, that is pretty important to most frameworks these days. And that's global state management for that. Everything we've been doing right now has been encapsulated into our various components. So for example, one of the reasons why I have the restaurant page you see here has all the data.
[00:00:19] And then I have this new restaurant form that is a subset and then being like sort of toggle back and forth. Is because at least prior just talking about state management it's very tricky to pass data around or components like that, right, so ideally I would create another route.
[00:00:34] And then that would share something in the global state, that's what we would ideally like to see. So what we're gonna do is we're gonna show you how to basically do some state management with TypeScript with the new library called Pinia. So if you haven't seen this cute little pineapple, this is the mascot for our global state management.
[00:00:52] And if you're wondering like, what about view x? Well, view x is ultimately, I think, basically going to be deprecated and Pinia is now the official state management solution. So Pinia Lite V3 is built from the bottom up with TypeScript, so it's designed to be really easy to use with it, actually.
[00:01:07] So what we're gonna do is we're gonna assume that basically people, this first time that people are using Pinia and then we're just gonna sort of augment types as like as we did before. So the thing about Pinia, as far as like, I think state management in general, is that in the past few years you used to have to opt into modularity.
[00:01:22] You have to be like, okay, like most people start out with this global store and then over time, you broke that up into smaller pieces that then could be chucked when it loaded on a certain page. You didn't load your entire global state on every single page. And that's kind of like the progress, but then I think at some point people realize, wait, I think we just want modular by default it's kinda silly to go the other direction to like, import everything at once.
[00:01:45] So Pinia follows that exact convention. So first thing first is inside of our stores directory currently empty. Let me make sure I'm on the right branch, I am great. We're just gonna create a new file, and I use Pascal case for naming the stores, just because they're kinda the constants the way I see them.
[00:02:00] So I'm gonna do RestaurantStore.ts, so I'm gonna do. Okay, now that we have this, what do we need to do? Well, we're going to import a define store helper from Pinia. So this is back to our composable discussion that we've been talking about where we have a lot of helper functions to help us do the view like things, but in a declarative way that people would understand.
[00:02:22] So now that we have this defined store, what can we do? Well, so we can define a constant. And what are we gonna call it? So it's a composable that's gonna call upon the store. So the use keyword is gonna be used here. So use RestaurantStore is gonna be what this is called.
[00:02:37] And then we're gonna call define store here. So this kinda like defined component if you think about it, now you're defining a component. Now you're defining a store, and so what am I gonna call it? I'm just gonna call it exactly what it's called, which is RestaurantStore. So I usually map the, this is the idea of the source essentially, and I map it directly to the file name, so that's why I name it this way.
[00:02:56] Casing is up to you, of course, now, with that said, now we have an object that we passed that is gonna consist of our stores. So typically in most state management like Redux and stuff you have your state, you have your getters, which are essentially your computer properties in view, and then you have actions.
[00:03:12] And then you have mutations, right, that's like your standard Redux pattern. But what I'm happy to tell you is that with Penia, mutations disappear, no more boilerplate of like, call an action to then define a mutation to basically do the same thing, that's all. We've taken care of that for you, it's all good now.
[00:03:34] So what this ultimately means is that to map it over to what we talked about before, this is essentially our data object from options API. This is our computed and this is our methods, basically what's happening. So first thing first, let's go ahead and then have a list of data.
[00:03:52] So to refactor this, I'm gonna teach here to do or restaurant page. And I'm gonna take all of this out. Okay, we're gonna x name that. And then actually I'll leave it as an empty array, so that it doesn't yell at me. And then we're gonna drop it inside of our restaurants store, okay?
[00:04:14] Lots and lots of yelling. Why is it yelling at me? Use store function that retrieves the store instance. Silly me, I forgot. State, in case you ever doing this in the past, you could define your state just as like an empty object. But it's actually just like most reactivity system, it needs to be a function that calls an object, so that it's always getting its latest instance.
[00:04:36] So once I turn this into the arrow function that returns an object that contains the state, there you go, now it is much happier with me. And so again, something that Tyffyn kinda helped me out with this because I would have just barreled ahead otherwise without that huge red squiggly line, okay, this looks much prettier.
[00:04:53] So now that we have this, of course, the question is, okay, how do we actually call the thing? So let's go ahead and inside of our restaurant page. We can go ahead now and import our composable use restaurant store and so what this is again what we're doing is we're basically setting it up.
[00:05:12] So what I can do here is I can say const restaurantStore = useRestaurantStore. And so this now has a direct correlation to that store module that we're importing on this file. And this is something that I personally have really come to like about using Pinia versus like view x where you just have this.dollar sign store.
[00:05:30] And you just have to assume things are aware and then you have to name things. This I just name it whatever that makes sense to me, which is restaurantsStore, use restaurantsStore I think most people can follow along this. And then from here, believe it or not let me lock restaurant store for us to see what we're getting, there you go great, other way, all right?
[00:05:49] So if I go to restaurants, nothing's here, but that makes sense. You'll notice that inside of our proxy we have basically everything here, there's the list. There's our list of all the things that we have inside of here. Which means no longer do you have to go like, store.state dot this or whatever.
[00:06:07] The ergonomics are there so that we can basically now say, restaurantStore.list. And so now if we refresh this and take a look, look, there's our proxy with all our events or the or sorry, all of our restaurants that we want. So really if we think about it, what we could do is we could just say that restaurantLists, we can just actually just do restaurantStore.List.
[00:06:34] And then cannot find any in our restaurant store, I think that was a weird compiler bug, [LAUGH] okay? And so again, what's nice though, is that now that you're actually in this proxy reactive like environment, all the values actually just disappear. Filter text is fine. So restaurant list filter this is great, but it's yelling at us.
[00:06:57] Why? Because right now we don't have any types, this actually does make sense. Because, again, we've defined it, we've used it, but how do we tell Pinia that we have a list of restaurants? Well, exactly just like we did before. I would just call it state shape and then I'm gonna say that, hey, the list is a list of restaurants, an array.
[00:07:21] And then when the object is returned, what am I expecting? StageShape. So now that I have that, when I go back to restaurant page, you'll notice all the squiggly lines have disappeared. And more importantly, I don't need value anymore, and that's it. I've now brought everything in, so let's continue on this refactor as well.
[00:07:42] So we have the ability to see the lists and all that stuff. So let's go ahead and now let's take a look at some getters, right? What are some common getters that we have? Well, we can clean this up quite a bit where we actually no, I think about it.
[00:07:53] We don't really need this, let's see, we don't need number of restaurants to live inside of this file. This is something that makes sense to live inside of the store, so let's move it. So we're gonna go ahead and take that out. Actually, let me just comment that out, so I don't forget that I need to do that.
[00:08:08] And then inside of here we can say a number of restaurants, right? And then this is where I have to recall real quick this is pull up the docs just to make sure I do this right. Because this is a discussion that's worth having real quick, okay? So something again that used to happen in the past is that whenever you would reference the state you have to use it in a normal function.
[00:08:32] You can use the arrow function because you're relying on this, but again in an effort to be a little bit more modular as you can see. Getters now can actually take an arrow function where they automatically get past the state as the first parameter. So now that we know that, we can basically then say, all right, well this takes an argument of state and what is it gonna do?
[00:08:53] Is gonna do return, okay, for now, this is actually, I know why this is gonna be a problem, but it's fine. We're this gonna practice this state.list.length, that's it. And then if you wanna annotate it, that's totally fine. We just add the number onto the annotation for the arrow function description.
[00:09:12] Okay, that's taken care of. And so this will be a little bit tricky later, but it's fine. We'll just crack on, and then here's the thing. Things like deleting a restaurant, adding a restaurant. How do we do this? Well, these are things that really belong inside, once again, the store.
[00:09:26] This should be something that we actually call an action for. So how does that look like? Well, in this particular case, again, just to match up with documentation, you'll see that it does something a little bit differently. Now is that because when you use this syntax just like I was mentioning, it actually needs to get the whole instance of the store now.
[00:09:48] We no longer can just get the state, and this is kind of tricky when it comes to auto completion and stuff. But especially because of things like asynchronous and that kind of thing. Typically, when you're running actions you're not gonna use an arrow function, that's just general best practice.
[00:10:03] But on the upside, what this means though is that Eduardo and the team who work on Pinia have made this thing actually still have autocomplete as you would expect when it comes to typing and that kind of stuff. So what we would do here is we would basically say, okay, so we have here that is gonna get the payload.
[00:10:22] And the payload is still gonna be a restaurant, that's fine, but then we can say this.list.push payload. We're not gonna worry about that right now. And then when we have delete restaurant, similarly, we'll get a payload of restaurant. And then this.list = this.list, .list.filter restaurant, great. We'll just type that and then there you go.
[00:11:00] So 1, 2, 3. So now we need to actually make sure we call all of these things inside of our page. So I'm gonna go ahead and let me just, I can just see the hide form is still something that's important. So this is where for example now, okay, so we have inside of here addRestaurant.
[00:11:19] So how do we do this? So inside addRestaurant, you'll notice that's being called right here, and we know that we're getting the payload. So what we're going to do is we're gonna still call this addRestaurant. That's totally fine, but rather than actually modifying it directly here, all we need to do is call restaurantStore.addRestaurant and then drop in the payload.
[00:11:43] Const save. Why is it saying can't find name addRestaurant? Okay, [LAUGH] I think we're okay now, that was super weird. Yep, no errors being yelled at, great. Okay, so we can test this out real quick basically by going into our app, refresh, much better okay now when we hit new and we do test.com I forgot to hit the sign it there we go.
[00:12:11] Now when we create it, there you go, it is actually appearing as expected. And so again, I could walk through the rest of this but I think the most important thing I wanna show you here with the global store. Is that it really doesn't look that different from a regular component instance, besides explicit keywords like State is the data that you're gonna be pulling from.
[00:12:35] Getters is computed and then actions. Like, this could easily be mapped to what we learned earlier today as far as like methods computed. And state and then StateShape, data shape. I think that's what's important to realize with in this journey with you and TypeScript, is that the goal is really not to make you learn something brand new in terms of syntax.
[00:12:56] It should feel very familiar as you are moving from thing to thing.