Let's Learn Vue's Composition API!
The new VueJS Composition API adds more power and a better developer experience to Vue. Ben Hong joins us again to teach us how we can level up our Vue apps using the Composition API!
Links & Resources
Click to expand the full transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
Jason: Hello, everyone! And welcome to another episode of Learn with Jason. Today, we're bringing back to the show, Ben Hong. Ben, thanks for joining us again. How are you?
Ben: Good. Glad to be back.
Jason: I'm super-excited to have you back. For everyone who didn't catch you last time, a background on who you are, what you do?
Ben: Sure. For those who don't know, I'm Ben Hong, I work at Netlify with Jason, which is awesome. I'm on the Vue JS Core team, working primarily with docs. Love the web community and super-excited to be here.
Jason: Super-excited to learn more about Vue. The last time we were here, we talked about the basics of Vue. An intro to Vue course. And what I am -- what I am excited about today, we're going to talk about a specific API in Vue which is new in Vue 3, right?
Ben: That is correct. You can technically use it in Vue 2. But it was introduced in Vue 3 as official support.
Jason: Okay. This is called the composition API.
Ben: That is correct.
Jason: Someone -- actually a few people have described this to me as being like React hooks, not a one-to-one API --
Jason: But in the spiritual equivalent to React hooks in Vue. Can you explain what that means?
Ben: Yeah, if we could bring up the project from last time.
Jason: Yeah, just jumping right into it.
Ben: To set everyone for the context in case they don't have the Vue background. It would help to show that.
Jason: This is the project we worked on last time. We made ourselves a wish list. Let's look at this and then boot it up real quick so we can see. Because I'm having trouble remembering what this thing did. All right. So, we have our main JS which loads our app. I'm going to see how much Vue we remember.
Ben: Yes, talk through it first.
Jason: So, it loads our app. Our app has a template, which is our wish list. It has a script. And then if I collapse this script, we also have style. And this is something that I thought was really cool about Vue is this concept of a single file component where you write your styles, your markup and your scripts all inside of HTML tags. So, the template tag, that's an HTML standard, script and style are HTML standards. You use them in everything. The template tag is a bit lesser known, but no less standard. It's supported in all browsers. Not something you need a framework for. Vue uses it in a novel way, whatever the way you want it, wrap it in the template tag. And they're linking these three together in the build process so they kind of execute in the same context.
Jason: So, our wish list here is a component that we're import and then we provide that to our app. And that lives in here. And this puts out what, you know, just markup. We have a div, headings, a list. And then we've got Vue specialty stuff. Vue 4, a loop, filterWishlist and then a gift. And let's see what else I can remember. Where did this come from? This came from computed. So, computed properties are different from data, but they both end up in props, is that correct?
Ben: Yeah. You can think of it that way, yes. They're available that's available to the template or the component to be used.
Jason: Okay. And then we set up filtered text, and we have an input. This setup is a model. When I call something a "Model" that's telling Vue to have a two-way binding, correct?
Jason: Okay. When we type into the input, it will keep the value of filtered text up into whatever value we need. This is nice about Vue. In React, we need a used state hook or another state or context to keep track of what that local state is. Whereas Vue knows that's a common thing and it makes it straight forward. You say Vue model, and give it this thing, an empty string and it does the rest. That's a nice API. That's pleasant. So, let's boot this thing and see what it does so that I can remember. I'm gonna run it with serve, is that right?
Ben: Yep. I think that's the easiest way to do it.
Jason: Okay. Serving. And... something -- what have I done?
Ben: Oh, it's a no SaaS finding thing.
Jason: Oh, it wants 12. I just need to switch to 12.
Ben: Okay. Got it.
Jason: nvm use 12.
Ben: Probably like an outdated Node SaaS.
Jason: I think last time I wrote this, I was in Node 12. I built it against 12.
Ben: Fair enough.
Jason: Let's see. I'm going to re-- I didn't open another window or anything. Okay. Here we go. I have a browser window. And let's drop this in here. Good. Okay. So, this is our -- our wishlist. And we have a list of toys, our retro toy list. And we can search for something like this. And it filters down for us and it all happens in real-time. And that's all done with this logic here. FilteredWishlist. So, filteredWishlist looks at the search text and ensures that it contains the search text. It's a very naive fuzzy search.
Jason: This is cool. We basically wrote a search filtering component in about 10 lines of code. This plus the markup to do it. And we didn't have to include any of that logic up here which that is something that I think can be confusing when you're working in React. A lot of times you see that logic tucked into the markup. The way JSX works, it's all part of the same components. A little bit of logic and some state up and then have your components, and in your components, write a filter for something. You can clean that up. But it's not like -- this almost feels like -- it's almost -- this makes the right thing the easy thing and setting up the right guardrails and providing the right basic. I know how to do that. I don't have to come up with a pattern or consult a React expert for the right way.
Ben: And so, the other thing to note about the computed -- scroll down -- one of the things it does well as far as extracting things, we want to watch reactive pieces and do things to it and update the set thing. Compute a process makes it easy. In this function, we're watching the filter text and the gifts. And we use those to compose what we want to ultimately return. It makes a declarative way of watching your data that you want to track and do computations on. And Vue does the caching and minimizing, whatever, and worry about performance and all you have to do is just use it.
Jason: Yes. And a quick little shoutout. Jeez, everybody. Nate, Pilo, Melvin, thank you for the subs, appreciate it. We have a hype train going. Here we go on to level 2 of the hype train. I don't know what hype trains mean. You get like motes, right? Ziggy, oh, my goodness. Here we go with. We're going. I'm so excited. Hey, now that you subscribed, you have access to the boops. Please use the motes. And you can do all sorts of fun things. You can rain boops on us. Also, if you use the corgi, there is some fun there if this is your first time on the show. So, Ben, did I cut you off in mid-sentence?
Ben: No, that's okay.
Jason: I was so excited about everything going on.
Ben: No, this is awesome.
Jason: This is interesting. What we have done with computed and submit vote method and some interactions happening here. Is this a good candidate to refactor to the composition API?
Ben: Yeah. I think this actually sets us up to discuss around the composition API.
Ben: What we have here, if you look at the component again.
Ben: When you see that we're exporting an object, we're defining specific configurations. Reactive data and computed products in the key, and if we want to expose functions, that goes of the method. This is known as the options API. As has traditionally been done. This is one way of writing the Vue apps. And the composition app is another way of writing. It has tradeoffs, but it's an alternative way of doing the same things you would normally do in the options API.
Jason: What I'm going to do so we don't break the old example is I'm going to make a copy of this.
Ben: Yes, perfect.
Jason: See if I can do this. C-a, let's-learn-Vue. No, that's wrong. What are we going to call this episode?
Ben: Let's learn composition API?
Jason: Yeah. That's pretty -- composition -- this is the worst -- okay. Is that gonna work first try? Please work first try.
Ben: Fingers crossed.
Jason: Okay. Do you think? So, the dash A should copy like all content recursively, get us all the folders. Oh, I should have deleted Node modules first. I have regrets, chat. I have regrets. All right. Oh, oh no. Oh, god. Can you do it? Can I get a compooper in the chat? Yeah, don't need to install at least. My guess is this will also manage to corrupt it when we work with Node modules. So, we'll see what happens.
Ben: I guess while we're waiting I can talk a little bit more about the composition API.
Jason: Yeah, let's do that.
Jason: Cool. Yeah, that makes sense. And I do like the idea of being able to sort of choose your abstractions, right? And as the name would suggest, kind of compose things a little bit --
Jason: As opposed to having to shoe horn everything into a pile. I suppose one thing to look at is just open up the composition API docs here.
Jason: Here, maybe? Or is there a better place for it?
Ben: Waiting for it. No, that's it. That's the official one.
Jason: Okay. So, I'm gonna drop this in the chat. And now I have... just the -- so, I'm not gonna read this. I'm going to let you be my docs today. I figured throw the docs out there.
Jason: What are we looking at when we get into the composition API?
Ben: Yeah, honestly, the easiest thing is I think just to do so code.
Jason: Okay. Let's do it.
Ben: Show people something that works. So, let's go ahead and just create a new component that's not wishlist for now. Let's do a counter.vue.
Jason: A new component?
Ben: Yeah, a new component. Not conflating things. Wishlist has a little bit of refactoring going on. We will refactor. We have the template block to start out with and give it an H1 of Counter.
Ben: Yes, we are going basic, f3ltron. And then to the script.
Jason: Okay. Why is it yelling at me?
Ben: I don't know at the moment.
Ben: What we're going to do, at the suggestion of the coding carter, before the E port, go to import, an object fromSingle quote review. Yep. Inside of the object, pull out the define component method. This is new with Vue 3. We have a lot of component methods, sorry, helper methods for things. Next to default, we're actually going to pass the empty object into define component. And this will help with some of the typings and that kind of stuff.
Jason: Oh, interesting.
Ben: Okay. This is just like a convention you might see closer to the Vue 3 sort of as components go on. Okay. So, given that now here's the thing, right? So, right now what we typically want to do is define a current count. Typically, define a data option. Data, colon. This will need to be a function that returns an object. The redux pattern.
Jason: This one does?
Ben: Yes. This needs to be -- the data needs to be a function that returns an object.
Jason: Gotcha. Okay. Gotcha.
Ben: Yep. And need double curly braces on top in order for that to render out.
Jason: No! Oh, god. Oh, god.
Ben: Oh, no!
Jason: All right. Here we go. Here we go.
Ben: All right. Now let's go ahead and I guess drop this on app.vue to see that it's running.
Jason: We will get our counter from Counter.
Ben: Swap it in. Great. Let's go and see if everything is working.
Jason: We should see a counter right at the top of this once it boots.
Jason: Be really cool if this worked first try. What don't you like?
Ben: That's prettier. Yell at us, deal with it later.
Jason: Yeah, got to let it cry itself out. Okay.
Ben: Wait, is it 8081? Do you have the other one still running?
Jason: No, it's 8080, but I'm wondering if I did something wrong. Did I...
Ben: Hold on. Let's see. It says stream -- I don't know what directory -- is this the right directory?
Jason: Should be. Let me check. We're in let's learn Vue composition API.
Ben: We are in it. Last time I think we were in the wrong directory. We were spinning our wheels.
Jason: That will happen. But no, this should be it. Let me make sure this is not still running. It's not. Okay. I just switched over to Firefox and this is like my favorite thing. Okay. So, maybe --
Jason: Let's try that one more time and just see...
Ben: Yeah. Run serve --
Jason: My counter, this is all correct.
Ben: Yeah, that's very standard. Import counter --
Jason: Just gonna pull this part out. Better work. Okay. What is -- how?
Ben: That's something... that seems weird.
Jason: Like it's -- there's no way this should function.
Jason: Let's learn Vue composition API. That's where I am.
Jason: Package JSON, serve.
Ben: Do we need to be in app?
Jason: I would be so confused by all of this.
Ben: Wait, wait, wait. Oh! Oh! We have that thing again. I am right. Scroll up. We're in the app directory right now. We're not in the root directory. I think you're serving from the root directory.
Jason: What the heck is this?
Ben: Remember? We ran into this last time. Okay. Go. Into app and re-- we'll figure out the logistics.
Jason: Wait -- what did we do? How did we do this?
Ben: I think we were showing everyone the Vue CLI GUI and scaled it. We're in the app directory. We'll clean it up later.
Jason: I'm cleaning it up now and we're deleting this folder. Get it out of here. Okay. So, I'm going to copy this one. I'm going to delete this entire folder. And then we're gonna come in here and let's get that. Here's our wish list.
Jason: Okay. Let's try that one more time and let's see if we can get this thing to cooperate with us. There it is.
Ben: Oh, I see something. Wonderful.
Jason: Wow. That was --
Ben: I was like, oh, this sounds familiar. This feels familiar.
Ben: Okay. Let's take this one step further real quick with the counter. Because you have a count, but we want to make sure that we show that it's reactive. If we go back into counter.vue.
Jason: Okay. Counter.
Ben: Okay. And then we're gonna define a methods property underneath data. So, we'll do comma and there will be a methods. Yep, just like that. It will be an object. And it will say increment count. We'll just call it something simple. And this will be a function. And then the only thing -- actually, I don't know if --
Jason: Is this right?
Ben: Not quite. This is one of the unique things about Vue. We need a this context. I believe the arrow function is going to break it. You need to get the this.currentCount. If you define in Vue or the options API, it will be -- yes, exactly.
Jason: Got it.
Ben: This is a limitation of the options API. As far as this context goes, there's a bit of magic helping us to know this is referring to the data current count.
Ben: People think this is complicated, but when I think this refers to the component itself, they're not thinking of the context in the way we think of this. Yeah, obviously I'm talking about this.currentCount which they can see online 10, for example. To your point, it's a complex topic that's got a lot of foot guns, to say the least.
Jason: You can. It's this.currentCount. It's such a fragile mental model.
Ben: It can be, yes.
Jason: I'm a very big fan. I used to be a kind of evangelist, but I've backed off because I think dogmatism is a silly thing to have as a developer.
Jason: It's not being explicit. I love the idea of any indirection, having an option to be explicit. I think that's a good path forward.
Ben: Yeah. Let's go ahead and implement this method. Just making sure everyone is coming with the same mental model. Let's get add a button element.
Jason: Wait. Let's see if I remember. It's... it's click!
Ben: Yes! Look at you go.
Ben: The only thing is, don't do the braces. We just do the double quotations because that's the JSX way. But since we're in templates, we don't need to worry about the JSX curly.
Ben: Save that and I think we're ready to go. Magic!
Jason: Look at it go.
Ben: Okay. Okay. So, now I think we are ready to go as far as refactoring this small piece and talking about the composition API.
Ben: So, the first thing we're going to introduce with the composition API, the way you know that anyone is using composition API, let's go ahead above data, let's go ahead and add a new option. And we're going to call it setup. And it's going to be a function -- basically, yeah. It's just going to be a function, actually.
Jason: Like this?
Ben: It doesn't need to return an object. It just needs to function -- yeah, it's just a setup function.
Ben: Like a life cycle hook. That's the way to think about it.
Jason: Got it.
Ben: And we create a available. And to expose it to our component, add two line breaks, and then return an object that includes currentCount2.
Ben: Okay. Now we can actually inside of our template, let's go ahead and switch currentCount to currentCount2. And we should see now on our page this has changed.
Jason: Yes. And I broke my incrementer.
Ben: Yes, the incrementer is broken. The method is pointing at the data option rather than the setup.
Jason: Like this?
Ben: What we need to do is we need to import a helper method called ref. If we go up into our import.
Ben: Yep. And then comma, add ref. That's all we need. And we're going to wrap the number 10 inside of a ref which is going to be a method and we pass 10 to it.
Ben: Okay. Great. Now that we've done that, actually, think of it like this. You've basically very explicitly told Vue that, like, hey, this value of 10, we want you to watch it. It's now going to be in a reactive reference. And so, inside of our const increment count, we want to actually update is, right? Vue 3 is using proxies as a way to manage all the reactivity.
Ben: We have to access currentCount by doing currentCount2.value.
Jason: Got it.
Ben: That's what you're changing. You're not changing the reference, you're changing the value inside of the reference. So, or you don't even need to -- oh, yeah. That's -- you don't need to do that, actually.
Jason: Oh, I don't need to do this.
Ben: Sorry, it's currentCount2.value plus equals 1.
Jason: We can modify it.
Ben: We can modify it -- yes.
Jason: I thought you meant it was immutable in the sense that once you put it into a ref then you many to create a new one and replace the old one.
Ben: No, no, no.
Jason: I got you.
Ben: Let's go ahead and finish this loop and dive into it. So, we want to expose this method now to the template. So, increment count 2 is what we're bringing up.
Jason: Just do that?
Ben: Swap it in and let's make sure it works.
Jason: Wow, okay.
Ben: That's it.
Ben: I thought so.
Jason: Because what I like about this is that I only need to learn one Vue concept --
Jason: To write this code. I only need to learn what ref does. And that lets me skip a couple -- a couple hoops here. So, with this one, I have to know that we exposed data. And then there's also this idea of computed data. And then there's also this idea of methods. And I have to know how each of those interact.
Jason: Whereas with this, I have one function. I'm sure that there are others that we'll talk about.
Jason: But kind of this general concept of saying, to do all of these things that I was doing before, I have my one function. I write the logic that I need. And then I return it. And then whatever is returned here is available to me inside of my component.
Jason: It's super-approachable. Understandable to me as a React developer. I might be bringing the curse of knowledge with me here. But it feels a little more like, oh, I can just run with this.
Ben: Yeah. And so, with ref, still, one of the things that actually, though, is a bit of a foot gun is the fact that you have this dot value concept. And you have to unwrap the value to access it. The one thing that I want to show you is instead, we're going to import a helper method called reactive instead of ref. Ref is what I needed to introduce, but I think most people will like reactive better.
Jason: Like this?
Ben: That's it. Instead of currentCount2, let's go ahead and do a const of state. Oh, sorry, with declare a new available called a constant of state.
Jason: Like this?
Ben: Const state. Yep. And equal to a reactive object. This is where you're going to pass an object into it.
Jason: And it would be like current count.
Ben: Yep. And inside here. Now just do current count, don't worry about the two. Yep.
Ben: There you go. And so, now you've told Vue basically hey, this object, everything inside of here is reactive data that I want you to keep track of. And now increment 2 can be just refactored to state.currentCount.
Jason: Like this.
Jason: Okay. All right.
Ben: And so, this is very declare -- this is what you would expect. The value, unless you're used to working with it is a very specific thing. Return state.
Jason: And I can get rid of all of this.
Ben: You can get rid of all of that.
Jason: Okay. So, this is nice. This gets in. And so, then this is gonna be state.currentCount?
Ben: Yep, that's correct.
Jason: All right. And this is going to be increment count.
Ben: Yep. Oop, oop, oop. Look at it go.
Ben: There you go.
Jason: That is beautiful code. I really like this as a general approach.
Ben: Yeah. And the final helper method I tell people about. Over time, you have reactive values in your state, it's going to be a little clogged as far as state.count, state dot that. You may want to destructure it to make it more declarative. You may be inclined to say spread state. Just prefix it with three periods. Let's go ahead and show that real quick.
Jason: Is that gonna work.
Ben: Hold on, I can't see your screen at the moment. Your GIF is dabbing.
Jason: What's up?
Ben: And so, yes -- you can full screen now. You're good.
Ben: This is reactivity. We have an import method, toRefs. Yep. This will basically take our reactive objective and take it to the template. Yes, the spread again, and then toRefs and pass state into it.
Jason: Okay. And so, then in here, I would just be able to use currentCount.
Jason: Okay. That makes sense. Still working. Doing what we want.
Jason: This is pretty slick, Ben.
Ben: Let's go ahead and talk about computer properties. That is an important concept to view.
Jason: Yeah. Before you ask that one, there was another question about components. Can you return components from setup? Or do you still need to use the components property in the component?
Ben: Yeah. I would still recommend defining components outside of it. Because it's one of those like it's the dependency. And it's honestly a little bit more declarative. But you can import components in setup. And there's RSCs regarding synaptic sugar for that. I would use the options API just like in the wishlist example with the components and pass it in. That's a nice way to be very declarative about what's going on there. You can focus on logic rather than just what you're bringing in.
Jason: The good part about that, you have infinite flexibility. You can do whatever. The down side to that, you have infinite flexibility and you can do whatever you want. That can get you into a lot of trouble.
Jason: I would concur -- whoa, everybody stop talking. Okay. My -- I have my speaker on, which is apparently going to talk to me now. So, when you're dealing with like working around APIs, try not to. Like use the API until you have a really good use case not to use the API. Okay. So, that is I think let's see. We were talking about -- you were going to talk about computed.
Ben: Yeah. Here's the thing, a lot of times when we work with data, a lot of times we want to do something when something changes, right? And so, let's say in this case we want to be able to show the like, the count doubled. And so, typically in I think most frameworks, you would find a way to watch the currentCount. And say if currentCount is greater than zero, then basically set currentCount or doubled count to like currentCount times 2. And so, that would be kind of the basic logic of what you would want to do.
Ben: But basic logic allows you to declare in a simpler way. You can do this in one of two ways. I will show the easiest -- not sure about the easiest. Let's declare new constant called double count.
Jason: Like just here?
Ben: Not inside of state yet. Yes. Show it out here, yep. Similarly, just like we had with reactive data and those sort of things, we need to use the computed property to watch reactive data and basically track dependencies and then update data accordingly.
Ben: We need the computed method from v3, import that.
Ben: Yes, hey, Vue, we want you to cache this stuff, right? Because that's the thing, it's easy to watch things. But then there's performance implications and those things. Here we would go ahead and type computed. What we do is we pass in a callback function which will then include what you wrote. Which is that state.currentCount. Or basically, it will return state.currentCount times 2. Just like that. Exactly. So, now let's go ahead and return that to -- let's expose that to our template.
Jason: And now I can do --
Ben: Yeah, let's go ahead and render it somewhere. Yep. And so, we save this.
Jason: It's doing what we want.
Ben: You can see, it automatically updates. And so, this is extremely powerful if you think about like this is one of the ways like Sarah Drasner does a lot of her animations. Watch this data and I'm going to do something else. That's beautiful. Rather than watch this data property, then do this -- the computer just takes care of it the way you want it to.
Jason: Yes. Here's what I want to do. I forgot to let everybody know about our captioning and sponsors before we started. We got so fast into it. I want to pause and talk about the sponsors and come back and I have a feeling that you have sugar to make this computing stuff even easier. Let me head over here. A quick shoutout. We have live captioning today. It is being done by Amanda from White Coat Captioning, thank you so much for being with us today. And that captioning is made possible by our sponsors, Netlify, Fauna, Internet Zero and Hasura. They're in the chat. They make this accessible to more people. I hope that means a lot to you, it means a lot to me. They're at Learn with Jason.dev home page and in every episode. Now that I've remembered to do everything I was supposed to do, let's get back to the computer properties and take a look at what we can do with them.
Ben: Great, earlier -- actually, one of the things I didn't mention is the thing about REST is as you use REST, you'll return object, you have to declare it every single time. Your return object just grows. I don't know about you all, but I like to keep things kind concise. And so, the double count, because it's associated with this state object, actually, we can actually refactor double count to be part of the object. If you add just a new property in that object called doubleCount with a colon. And then the same thing, computed with a callback method of what you wrote. Copy and paste that over.
Jason: Interesting. Okay.
Ben: Now delete line 17 and line 26 and everything should work exactly the same way.
Jason: Oh, that's nice. Okay.
Jason: Yeah. This is slick too. You can see the -- so, in React parlance, this would be like a custom hook. Where you wrap up this data, all of this logic that's happening here. And I could -- actually, can we do this just to show how this would work.
Ben: Sure. Go for it.
Jason: Okay. I have a theory and tell me if this works. Is there a naming convention for if you have a custom composition thing? Do you -- in React you call it a hook, right?
Jason: I don't know, I'm into that, call it dashables. And just Vue or --
Jason: Now what I'm going to do, export, we'll do the default. And we'll say, is there a naming convention for this? Or just whatever you want it to be?
Ben: Basically, do it the way you want. Go about your instinct. We can work through it after that.
Jason: Okay. What I'm going to do here is I'm going to make this into an arrow function. And I'm going to put all of this here. And then I need to go in and we got to import some of this stuff. So, let's grab these. And we don't need the define component. Okay. And so, now what I've got is I have my Vue helpers and I've got, like, a pre-built setup function that is kind of --
Jason: Set to do what I want it to do. So, then when I come in here, instead of doing all of this, I should be able to import --
Ben: So, we also, the use convention is common. So, you might call it use counter -- yeah, counter store, that is fine. That is becoming popular as well. Similar to the React hooks.
Jason: Okay. Then what I could do is I could say, like, counterState equals... useCounter. And then down here, I can spread counterState. Oh, but maybe without the double spread. And then theoretically speaking now, I have a custom hook. There we go. So, now we've been able to encapsulate this in such a way that if like we need logic, if we need a debalancer, if we need -- we want to do some animation helpers or math helpers, these are now available everywhere in our app in a way that is easy to define and then share around.
Jason: I like that. I think that's very nice. And that does -- as you say -- it remove this is need to put everything into a component and then to kind of like pass this stuff down between components or whatever -- these are all the same issues that you run into with any kind of component-based framework.
Jason: How do you deal with global state versus prop drilling versus, you know, incredibly complex nested hierarchies? These types of abstractions let us get away from that. Because now this is -- we can like reason about this. I can go look at this and see what the logic is that makes this thing tick. And I don't need to worry about how it's used because that's separate from how it's computed. And I think that helps me reason about the code. And I like that a lot.
Ben: Yeah. And something that's worth noting for people. Because Jason used a function to return an object, I believe this is the factory pattern. So, this will generate a new counter store, as Jason puts it.
Ben: But theoretically, if you return the object itself of the state and the increment, this can become shared state. So, we can actually create something where --
Jason: Oh, you're saying I can put something up here.
Ben: Yeah, you can move that out.
Jason: And then I could do...
Ben: Remove the function, actually. Return the object.
Jason: This here.
Jason: Instead of -- so, it really wouldn't be a...
Ben: useCounter. Yeah, it would be counter. And then you would to destructure what you want from it.
Jason: Would that work right?
Ben: I don't think so. Only because you're -- I think... let's just try --
Jason: Give me the whole object. Let's try it.
Ben: Let's try it. Okay. That's what I thought. No, I think you actually have to -- usually if I'm destructuring it, I want, again, current count and increment, I usually destructure it directly. Because I think it's the same thing. Because if you destructure like that, I think it removes, call it like the attachments. That should be part of it now. There you go. Refresh...
Jason: It doesn't like something that I'm doing. Object is not a function.
Ben: Are we calling it as a function somewhere?
Jason: I didn't think so.
Jason: Export, default. Am I doing something, chat? What am I doing wrong?
Ben: toRefs? Can we log counter? Just to -- not in here. Just inside of the setup function.
Jason: Yeah. Let's do it.
Ben: Just make sure it's pulling in Counter correctly.
Jason: Object. There's my counter count.
Ben: Oh! Oh, it's a dot counter. It's named counter. It's counter.counter. Right? Because it's an empty object.
Jason: No, I named it.
Ben: You named it. Never mind.
Jason: Let me drop it in without that and we can take a look. Wait, is it working now, hold on. Yeah, now it's working.
Ben: Do we need just a hard refresh?
Jason: Yeah, maybe.
Ben: Yeah, I think maybe a hard refresh thing.
Jason: Yep. Now --
Ben: Okay. There grow. Now theoretically we can import counter across different components. And when you increment it on one page, it would change somewhere else.
Jason: And we could also do this like, I think, this way, right? And it would -- it would let us do it as useCounter, then? And it would get the shared state?
Ben: I think so. I think you're right. I haven't played around with this particular Counter. Had
Jason: I think -- it's a little clearer to me when we do it like this.
Ben: Yeah, go for it.
Jason: And then we've got --... I'm just a little confused why this doesn't work.
Ben: Yeah, let's go back and see if I --
Jason: It should just work.
Ben: Let's try to destructure it now.
Jason: It works. I must have typo'd something before. Because whatever it was, it seems to be working now.
Ben: I'm sorry, I think NMeuleman, yes, sometimes it needs hard refresh depending on how the imports work. That's what we ran into.
Jason: Back to zero, boom, boom, boom. There we go. This is pretty slick. This is a nice pattern, feels good to use. Feels approachable without being a Vue expert. I'm immediately aware of what's going on. Which I think is a nice feel. And like these -- these I would have to look up what these do. But your explanation was fast enough and clear enough that I don't -- I get it. I get what's happening. So, there's not a huge amount of framework-specific context required to get started with writing in the composition API. Which I think is a nice -- it's a nice touch.
Ben: Yeah, one of the interesting things in Vue 3 is a lot of it's broken out into very small libraries that are not only tree shakable, but basically you can break apart Vue 3's and use it in a React context. It was meant to be modular. One thing was that was talked about was using Vue 3 on the back end and update things and changes. To your point, it is supposed to go away from being Vue-specific, but being able to be composed in different context.
Jason: Yeah. And chat let us know that the reason that didn't work was we forgot to save counter JS. Thank you for pointing that out.
Ben: It's the little things.
Jason: Yeah, yeah. I think there was a question, though.
Ben: Oh, yeah, let's see. About state management.
Jason: Let's find out. If you had a question and we didn't answer it, throw it back -- oh, state and composition API is similar in the state.vueX.
Ben: This is coming cross, we say we don't these VueX name. Anything in absolutes, there are tradeoffs. I think VueX has a lot of value, we think of redux and state libraries. Being able to time travel and see exactly happened over a mutation and actions. It can be important when you have all of these events happening at once. But it opens up ways of sharing state and moving it in ways that don't need to be in a global singleton like a redux, for example. But that doesn't make it obsolete. I want to be clear.
Jason: Good point. And American2050, the reason that I didn't spread useCounter is personal preference. It would totally work. My -- I -- there's like an internal alarm that starts to sound when I get too many layers of destructuring in. Why I'm like, this is too hard to read. I add intermediate variables to explain to myself what I'm doing. So, all right. Let's -- should we refactor -- let's refactor the wishlist.
Ben: Yeah, I would like to -- based on what you've learned now, how would you rewrite the wishlist?
Jason: Here we go. All right. So... I'm gonna create a setup function. That function is going to need to return -- let's... we need to return gifts. Which we'll just get from here. Return that straight up. We need to return filter text. Which will be reactive -- no. That's not right. It needed to be -- oh. God. Oh, wait. I can do this. We're going to need our state, which is going to be reactive. And that's going to have filter text in it.
Jason: Okay. And then down here we're going to spread useRefs of state. That means I need to import all of that, which I'm going to do from over here. Whoops. Okay. ToRefs, not useRefs. Okay. Now we need to compute our list of gifts. That's filtered wishlist. So, I'm going to take all of this, and I want to move it into here. Filtered wishlist. And that is gonna be a function. And that function is going to take... gifts. It's gonna return that. So, it will return... did I miss like part of that? I did. Okay. So, we're gonna -- the filteredWishlist is going to return off of the gifts. Label lower case. And then we're going to use the state filterText, which is this one here, lowerCased, and return whether or not the gift should be there. So, that should work. And because I am dropping it down like that, I actually don't even need to return the gifts anymore because we don't use that.
Jason: So, instead, we're going to get our filteredText and our filteredWishlist. Which will be filteredText, filteredWishlist, and I don't think -- I need to do a submit vote. But we can worry about that in a second. But in the meantime, what I'm going to do is I'm going to get rid of these. And it doesn't work. Doesn't work because?
Ben: Because what.
Jason: Computer is defined, but never used. Because I didn't compute this.
Jason: I cannot not write computerred.
Ben: Computerred. That should be an include.
Jason: Look at it go.
Ben: look at you go!
Ben: Yeah. That really is all it is to it. I see some questions talking about recursive in arrays and object. And, yeah, the Vue 3 reactivity just tracks all those things for you. So, the moment you drop it in, it will do the thing you want it to do. Yeah.
Jason: Yeah. That is really slick. Dang. I'm -- I'm pumped on this. I'm really -- I'm feeling it.
Ben: Yeah. That's it. That's all there is to it. I mean, you know.
Jason: Methods here.
Jason: I could just return this from, right? Like, I don't necessarily need this to be in methods?
Jason: Let's see. Object ID, object ID. It's working.
Ben: You can see, this is the way it worked.
Jason: Okay. Now I have an idea. Because we've got a little bit of time left, we have about 30 minutes.
Jason: And I have a more complex use case that it would be interesting to see how you would approach this.
Ben: Go for it.
Jason: We want to be able to wish for these items, right?
Jason: To do that, we need a counter. But not a counter that is global. But a counter that is tied to these object IDs.
Jason: So, to do that, we would need some kind of a map of like each gift ID to a count.
Ben: Yep. Uh-huh.
Jason: I feel like we can use the composition API, I feel like we can use state. So, we could do this a bunch of ways, right? We could put it all in here. But we could also use a separate state.
Ben: You could. It's up to you.
Ben: How would you do it? Like what's your instinct? I think that's the first way to start with this.
Ben: Yeah. If you look into the actual gifts.JSON they did, I think the data structure in there already includes a count, I want to say.
Jason: It has a vote.
Ben: Vote, sorry. That's what we want. That's what it is.
Ben: Theoretically, you want to increment in votes, is that what you're saying?
Jason: That's a good point. This is interesting because we're treating these as if they're coming from a database. We don't have a database to update. We could potentially set one up in 30 minutes, but that would be rushing to the point that --
Jason: So, let's think about how we would solve this. In a -- so...
Ben: Because you're saying, what if we didn't have votes, and you wanted to update each item?
Jason: I was saying, without the votes, trying to hold this in state. Eventually we want to send these back.
Jason: Maybe a step further, how to use the Vue composition API with databases and maybe you can come back and we can refactor even further and we'll hook some serverless functioning up or something.
Jason: For now, say we don't have any data store, and instead we need to store it in state. Ignore that votes for the moment. And write this as if we need to track it here.
Ben: Yeah. Let's go inside of wishlist.
Ben: Because we have a starting point. Like, we have data. This is where we would actually go ahead and say, inside of state, I would add a gift the, call it gifts list, and import that to the imported JSON. So, giftList: Gift.
Ben: And so, what we have now here is a fully reactive object. So, if theoretically we needed to append a new property. Calling it likes instead of votes. Pretend it doesn't exist. Below with state is, do a new line on like line 36. To your point, right? We could just do state.giftList.map. And then we'll map every item, which we'll call a gift. And then we're just gonna do, you know, and for every -- gift.like, let's just start it at 8. That's -- every gift like will have 8 to start.
Jason: To map terminology, I'm going to use wishes for the wishlist.
Ben: Sounds good. I think now, if we just go ahead and render that in each item as like a -- so we can show it.
Jason: Okay. Something is happening that I need explained.
Ben: Oh, yes.
Jason: We are using map, which would return. But we're not doing anything with that. Should we use forEach in this case?
Ben: Yes. I think you're right.
Jason: Okay. And now because this is reactive, this is going to -- this will update.
Ben: This will update. Yes, it should update correctly.
Jason: I'm going to use giftWishes instead of votes.
Jason: Now everything has 8 wishes. Okay.
Ben: That worked exactly as we hoped. Now you want to be able to modify each one as we pressed button.
Ben: Theoretically, if we go down to our declare function -- what did we declare? Increment vote? Submit vote? Let's pull that out.
Jason: Add wish.
Ben: Add wish.
Jason: A add that up here. And function addWish.
Ben: And we need the ID. We need that. And this is where we write the mini search function. State.giftList.find I think is what we probably want. And then for every gift --
Jason: Gift.id equals id.
Ben: And then basically, yeah, if that thing is true, if giftFound is true, then return -- yeah.
Jason: So, we could just get away with this because we're able to mutate.
Ben: Yeah, it's all React -- Vue is tracking everything. So, at this point, find it and then add the thing.
Ben: So, we just exposed it there. Great. And if we scroll up now to the top. We need to switch out the function on the button. I don't think we have it attached to the right thing.
Jason: That's right. Wait, didn't I do that? AddWish. Gift it.
Ben: Right. So --
Jason: This doesn't need to be computed or anything?
Ben: No. But can we log out ID real quick to make sure we know what's being passed in? Great. So, now when we click -- okay. Great.
Jason: Here's our ID.
Ben: So, ID...
Jason: And let's also log --
Ben: Gift. To make sure.
Jason: And it is finding our gift. But it's a proxy. Which means that we need to get its value?
Ben: Oh, gosh, this is where it's a little bit trick -- yes. I think -- let's do that for now. Try that. Gift.value.wishes. Oh, gosh. This will be another one of those gotchas. You may have to use useRef. Okay. Oh. Alex is in the chat. And we have the save. But let's go ahead and --
Jason: Gift.value.wishes? Like that?
Ben: Oh, oh, yes. Gift or to --
Jason: Doesn't like that at all.
Ben: Oh, I forgot now. You found the item. But then --
Jason: Question aren't able to modify it because it's a proxy.
Ben: Yeah. So, I think it's -- to Alex's point, I think it's toRef that we need.
Jason: Gift.value. I saved! Oh, I didn't say. Okay, here's our proxy --
Jason: Okay. If I do const giftList equals toRefs, state.
Ben: I don't think we need to go that far. Because gift -- wait, why are we going giftList? Shouldn't it be state.giftList?
Jason: I was saying we did the two --
Ben: Sorry. Got it.
Jason: And then destructure. But it doesn't like that. What doesn't it like? Unhandled error. Gift --
Ben: Is not a function. Yeah, what is giftList? Let's double check that real quick.
Jason: So, here's our giftList. It is a proxy.
Ben: Okay. If it's a proxy, then we probably need .value.
Jason: Okay. GiftList.value.find. Okay. So, it likes --
Ben: That's much better.
Jason: Yeah. But it doesn't work.
Ben: So, it doesn't return -- wait. Can we log gift now?
Jason: Yep. And I will log it after we set the wishes on it. And so, it's a --
Ben: Another proxy.
Ben: Do gift.value.wishes plus value plus one.
Ben: Then this -- no -- didn't like that either.
Jason: Gift.value --
Ben: Okay. this is weird.
Jason: We're doing something kind much funky here.
Jason: Which is we are like taking a data object, sticking it into state. And then we're like pulling it out of state and trying to mutate it. And I'm wondering if it would be easier to like get the whole object and then I don't know. Reactivity is a little confusing to me. Because mutation is not the way my default is.
Ben: Yeah. Const gifts, okay. Julien is suggesting in the chats, const gift toRefs, state.gift -- this is what I was going to try.
Ben: Yeah that might be the problem. Delete line 43. It's one of those, once you break the proxy, then things start to go haywire.
Jason: Say all of that again. Now I'm confused.
Ben: Yeah. It's fine. So, earlier we were trying to -- you had pulled out gift lists from state, with toRefs for a second, and then deleted it. Ignore me for a moment.
Jason: The other thing I'm confused about here. Like this just worked, right?
Jason: What's different now in here?
Ben: Because we're assigning a variable to gift. I think that's the problem.
Jason: So, if I instead do like a state.giftList.find. And we'll do gift and do like id equals gift.id and then I just do wishes, plus equals 1?
Ben: Oh. Interesting.
Jason: Well, because that's what we did here, right?
Ben: Yeah. Right.
Jason: Will it let me do that?
Ben: Yeah, let's see what happens. I think it's going to -- okay. What happened?
Jason: It doesn't break, but it doesn't do anything. And I'm wondering --
Ben: Can you... just for -- just to try, add the .value. Because if it found a ref and then...
Jason: I want to see if we get like -- what does it give us out of this value?
Ben: I see, I see, yes. Let's see.
Jason: And that just gives us straight up the number. Which is incorrect. That's not what we want. So instead --
Jason: We want...
Ben: Can you... find a new object and then you're losing. For those watching, we're dealing with sort of the reference getting lost somewhere in call it the assignment of a new variable.
Jason: So, do I need to like orEach this instead?
Ben: I know for sure we could literally do forEach, if that happens, then increment. That 100% works. But I was trying to make the find work. But, yes, this should 100% work. It should. In case it doesn't. I'm pretty sure. With a fair degree of confidence, I'm pretty sure this would work. You don't need the val anymore. And yes, save.
Jason: Hard refresh.
Ben: Why isn't it liking it?
Jason: Yeah. What's -- what's the deal?
Ben: State list for each, if gift -- yeah, why is it not incrementing it?
Jason: Let me double check that I'm getting the data, the ID, yeah, the ID is right. So, that should match.
Ben: Can we, I guess, log out their -- what the object is? If then logged gift, I guess?
Jason: Let's make sure we're getting the Right gift. So, here's a Furby, wish for a Furby. Get an ID. We get a proxy. And the target of that proxy.
Jason: But look, it updated.
Ben: It did update.
Jason: So, our reactivity is doing something weird and I don't know what it is. Do we have to wrap it in computed?
Ben: This is going to sound weird. Can you do gift.value.wishes? I wonder if the point you've broken down, is it a ref?
Ben: No. So, something is weird.
Jason: It's gift --
Ben: Linda is saying, is gift.wishes actually set as reactive?
Jason: That's what I was thinking. I don't think we're making this value reactive.
Ben: I think most likely Alex has tried something that does work.
Jason: Didn't the toRefs blow up on us?
Ben: ToRefs, it's a singular toRef. I'm trying to find the docs on this.
Jason: So, like this, then?
Ben: Oh. Okay. I think --
Jason: That seems like it's going to explode. Let's actually import this, though.
Ben: Yes. Please import that. Oh! Oh! Okay. Yeah. Okay.
Jason: Like that either.
Ben: Hold on. Let's try this. Let's go back -- forget the forEach. Let's go to the find. This is what most people want to do. We did const gift equals, and then the toRef, and pass it in. And we're passing in state.giftList.find. Yep. And then --
Jason: Okay. So, now we have our gift is a ref. That mean this is item --
Ben: Yep. Let's lock gift to make sure I'm not totally wrong about this.
Jason: Okay. Here we go. We're doing it.
Ben: We're doing it. We got this.
Jason: Still a proxy.
Ben: No, that's good. That's actually really good.
Ben: Now -- because now we just have a ref, it will be gift.value.wishes + = 1.
Jason: Gift.value is undefined.
Ben: Wait. How?
Ben: Trying to use -- and the source property doesn't work. Optional props. Ah! No!
Jason: Yeah. So, the part that's confusing here is that it's pulling in like the value is correct, right? So, you've got this target. And then the wishes get updated -- or they don't now because this one's broken. But they -- they are getting updated and then the UI is not updating. So, that has me thinking -- what's going on? Must use .value to read or write. If we go here, right?
Jason: This sets the value. Oh, stop helping.
Ben: Gift wishlist --
Jason: We get our proxy, we get our target. And then see that the wishes are updated. So, this doesn't feel like the logic in this function is wrong. It feels like our reactive is not flagged properly. Does this need to be set as something because it's nested inside of gifts?
Ben: No. By design that shouldn't have changed.
Jason: Yeah, so, like here, do I need to make this reactive?
Ben: As all the pieces are already refs.
Jason: I'm just gonna push buttons. Nope. Doesn't like that.
Ben: That shouldn't be it. The whole point -- it's the whole recursive thing, it's supposed to watch it for you without you being specific. That's one of the things that Vue is supposed to do for you. So, my question ends up being why --
Jason: Do you suspect it's because we're using the filtered wishlist? Do we need to update the filtered wishlist?
Ben: That is completely separate.
Jason: That is actually -- no, it's not.
Jason: Oh, my god. Oh, my god. Is this it? Is this the thing?
Ben: That would be so weird that was conflicting like that.
Jason: Here's why, we weren't using the gifts.
Jason: Yeah. Okay.
Ben: Ahhh... well done, Jason. Well done! Where's the championship belt?
Jason: Okay. First try. Yeah, there it is.
Ben: So, it is working -- oh, my gosh. And I was like, I swear, it's got to be easier than this. Okay. So, right. Because we were modifying every single -- yeah, yeah, yeah. Okay. Yeah, that makes sense.
Jason: Yeah, so, we -- okay. But this is actually -- but this is important. We just learned something that I'm never going to forget now. Which is that when we are using state like this, if we're reaching outside of the state, we can't do computer properties off that. So, this filtered wishlist has to be dependent on reactive properties or it will not update the way we expect. By putting this in here, originally, I was hoping we wouldn't have to do that, but when we have dependent states, we have to. We have to use this reactive gift list. We can't use the parent gifts.
Jason: Okay. Hard fought, hard won lesson there.
Ben: I was like, I don't under -- for those who might have missed it. Basically, we didn't have the state.giftList in line 28, we were modifying the import. It was throwing a wrench in everything.
Jason: Originally what we were doing, using this, and imported from here.
Ben: And also passed from 25. You had this calamity of not knowing what's what. So, we scrolled down a bit just to make sure now. Yeah. Linus is like, you didn't mutate the proxy. Yeah. So, we can see here now, we found it, modified it, and now it works exactly as we thought.
Ben: Didn't have to do any weird toRef or anything. This just works now.
Jason: Yeah. So, aside from the fact -- if we take this from the -- skip all the parts we were doing it wrong and look at this happy path here. This is very approachable. We, you know, we just do what we would expect. We get our gift list from the state, we run the find on it, we make an update. You mutate the object in place. And it works. So, this is actually really pleasant to use with that gotcha that you have to make sure that everything you're modifying is in fact tracked in this state. Map, that is really nice.
Ben: I think there were a couple questions. So, react sieve a style. You can use all ref was. If you want to manually declare every single reactive as ref, that's up to you. Do you have to put computed inside of reactive? Nope. That's a way to organize it that's open to you. But you're 100% entitled to say const everything and manually export it. I find that the grouping is a good way of grouping similar contexts. As your components inevitably get more complex, you can group things together in a way that makes sense if for your logic.
Jason: Yeah. Is it using something like emmer under the hood?
Ben: I don't know what it is.
Jason: Probably not.
Ben: Every time I've heard the reactive I underneath, it's using proxies. It's not proprietary. That's why it's limited back to IE11, for example. Proxies are not native.
Jason: Okay. So, Ben, this was super-fun. I absolutely want to come back and take this even further with some serverless functions.
Jason: Let's make a UI that remembers what our state is. Right now it's hard-coded.
Ben: That it is.
Jason: Every time we come in and refresh the page, that's starting at zero. We want to persist that. Next time, maybe we'll stand up a database, stand up serverless functions and make this persist. Release there into the world and let the chat wish for Furbys to their heart's content. For someone who would want to be further, where to start?
Ben: There's the official docs, v3.vuejs.org. Composition API is very new and we're trying to explain it in ways that are approachable. If you read something and you're like, that doesn't make a lot of sense, please file an issue. Reach out to us. We're looking for ways to improve the docs. We have a whole team dedicated to it on the Core team. Feedback is always welcome. But in case you want more practice, on JAMstack explorers, if you could open that up, Jason, we have a whole composition course for you.
Jason: Yeah, that's right. You just did this.
Ben: Yeah, released a couple weeks ago. It felt like longer. This is a video format that Jason and I and the team got together.S it beginning to end. If you need a re-walk through over to compositions API, and they cover more in there regarding life cycle hooks and the watch method. Check those out and there are exercises that you can try out. I'm always a resource. You can find me on Twitter on the usual thing. Happy to chat.
Jason: Y'all, I'm not going to play the sound on this. But I have to point out how upstaged we have all been by Ben. Look at this. He got like this astronaut and did a robo voiceover. I'm very unhappy because I have to put so much more effort into my Explorer's courses now. Thanks, Ben. Gave me chores. Okay. So, Ben, that -- that is a couple great resources for people getting started with composition API. Where should folks go if they want to follow you? I'm going to pull up your Twitter right now.
Ben: Yeah, BenCodeZen. It will be harder over time, but I pretty much own BenCodeZen on all the things. Twitch, YouTube, Instagram, GitHub. I have been consistent with this branding across most platforms. But also, yep, that's my site there, Bencodezen.org.
Jason: That's great.
Ben: Designed by Dave who designed the Bower logo if you don't know that.
Ben: Bye, bye!
Closed captioning and more are made possible by our sponsors: