Building a Basic Input0:00
All right, next up, why don't we keep working with composables? In this video, I will show you a new example that makes use of, of course, the Composition API, as well as Reactivity. All right, so here's what I'm thinking. I'll set up a basic example here. What is your favorite food? And then we'll have a simple input. Now, of course, I want to track the user's answer, so I will add vModel, and we'll call it food, and then I'll set that up as a reactive property. So I can say let food = ref(), and that will default to an empty string.
Saving Input to LocalStorage0:59
And this is actually very common for things like forums, where you might potentially write a long reply, and if something happens, if your internet goes out, you lose all of your input. Let's see if we can prevent that. And as always, I will show you this in two steps. First, we will do it inline, and then second, we will extract it to a composable. Now your first thought might be, well, let's listen for when the user types into this input and when they do write to local storage. So there is an input event we could use. I could say when the user types into it, let's call a function write.
So there is an input event we could use. I could say when the user types into it, let's call a function write. All right, so we could define that here, write. Now if you're familiar with the localStorage API, I could say setItem, we'll call it food, and I'll make it equal to the value for this ref. So food.value. And yeah, that should work. Just keep in mind, in real life, we should throttle this. At the moment, we are calling this write function for every keystroke, but it's probably not necessary.
At the moment, we are calling this write function for every keystroke, but it's probably not necessary. Maybe you call it once every few seconds or something like that. Okay, but anyways, just an example. If I bring up the application tab, I will click on Storage, and then down to our current site. Okay, and of course, we don't have anything in localStorage. Let's see if we can fix that. Pizza, and there we go. So now, even if I refresh the page, of course the input is cleared, but we do remember that.
Restoring Value on Load2:18
Pizza, and there we go. So now, even if I refresh the page, of course the input is cleared, but we do remember that the user typed pizza into that input. So now, how can we remember it on page load? We basically want to say, okay, when the page loads, check into localStorage, and if you have something for this input, then set it as the default value. Okay, well maybe we could do something like localStorage.getItem for food. And let's see if that works. Come back, have a look, and there we go. It works.
Come back, have a look, and there we go. It works. But if we go to application, I can right click here and delete it. What happens if we refresh and there is nothing in localStorage? Then it's blank, we type into it, everything seems to be working. Refresh and we've remembered. But now, let's say I want to do this for multiple inputs. So I could say, I don't know, how old are you? And this will be v-model for age. All right, let's set that up, see how it might work.
And this will be v-model for age. All right, let's set that up, see how it might work. We now have one for age, where we get a key called age from local storage. Next, the write function can't be so generic. We're going to need a key as well as a value to write to. That way I can substitute these. Okay, so now when we type into this input, we could write to food the value of food. All right, and then the same thing here for age. All right, let's give that one a shot. So we'll say here, pizza, maybe you're 12 years old.
Switching to Watchers4:38
it would be nice if we also update local storage in the process. But right now, if I give it a refresh, one, two, we check local storage, and of course, that's not changing anything here. Okay, well, one way we can solve this is instead of listening for an input event, let's just watch for when the value of food changes. So if we take that approach, I get rid of all of this, and same thing down here. And now, to set up a watcher, we're going to import watch from vue. Or if you're using the options API, you can just add a watch object directly to that object. So I'm going to watch food, and when it changes, I'll have the value here,
Extracting useStorage Composable6:10
extracting a reusable composable. Okay, so open up the sidebar, I will add a new composable. I'm going to call it useStorage. And as we've learned, I'll hide the sidebar. We should export a function with that same name. That will return an object that we can receive from our component. All right, so let's switch back to Home.vue. We now know that we're going to import useStorage from our composable. And then ideally, if I get rid of all of this, yeah, I want to say something like, all right, I want to useStorage for food.
And then ideally, if I get rid of all of this, yeah, I want to say something like, all right, I want to useStorage for food. And then I will catch that into this variable. Yeah, ideally, that's all I need to do here. And as you can imagine, this will look into localStorage. It will try to find something for the key food. If it does, it returns it, and maybe it should also return it as a ref. That way, we can instantly use it within our input. Okay, so let's keep it very simple for now, get rid of all of that. And if we try it, of course, nothing is going to happen because useStorage is blank.
Okay, so let's keep it very simple for now, get rid of all of that. And if we try it, of course, nothing is going to happen because useStorage is blank. So let's write a little code before trying this out. We're going to accept a key, and we've learned that we can run localStorage.getItem for key. Now, if there is anything in localStorage for that key, it of course returns the value, otherwise it returns null. So why don't we save this to a variable like storedValue, whatever is in localStorage. But then ultimately, I want to return a ref to the user.
Right now, food is null. We type into it, and yeah, we are tracking that, which is great. Okay, the next step is to take what the user types and also write it to localStorage. So let's switch back, and we learned that we could add a watcher. So I will import watch from vue. Remember, this is how the composition API works. We import the APIs that we require. So let's watch that value, and when it changes, I want to write to localStorage.
So let's watch that value, and when it changes, I want to write to localStorage. Okay, let's switch back and find our write function that we wrote earlier. I'll cut that out and bring it, how about, right down here. But this time, write doesn't need the key and value. We already have it, so let's get rid of that. The key was passed in when we called the function, and the value is our ref here. So just keep in mind, when you have a ref, you always got to tack on .value, which makes this property name a little weird, val.value.
So just keep in mind, when you have a ref, you always got to tack on .value, which makes this property name a little weird, val.value. Maybe we can think of a better name later, but nonetheless, I think this should work. All right, so think about it. Let's go through this. At the very top, I say, all right, I want to use storage for food. So immediately, it looks in localStorage to see if we have anything for it. And if we do, we wrap it in a ref. And in fact, if we don't, we also wrap it in a ref, but it's just set to null.
Handling Defaults and Edge Cases9:49
So have a look. We are keeping food in sync, which means if I were to change it here to tacos, well, that change should also trigger a write to localStorage. So back to application, and now notice that's been updated. All right, pretty cool. But now a few edge cases, like what if we empty this out? Well, now we have food equal to nothing. So what we could do is say, look for the value. If it's an empty string, then in that case, maybe you want to call localStorage.removeItem key.
So for example, maybe I want to say the default value for this, if it's not in localStorage, will be salad. All right, let's see if we can get that to work. So now, our signature now accepts a default value, and it looks like we'll have to tweak this a little bit. So let's say, all right, well, if there's a stored value already, then maybe that's the one we want to return. So I could bring this up and say, okay, well, if that's the case, then overwriteValue equal to ref for the stored value. Otherwise, we'll set value equal to ref for the default value.
then overwrite value equal to a ref for the stored value. Otherwise, we'll set value equal to a ref for the default value. And then, well, we would also need to write to localStorage, because it doesn't yet exist there. So we create our ref, and then instantly save it to storage. So I could call write. All right, let's see if that works. And it looks like it did, just so we can see it live though. Let's delete it, and then manually refresh the page. And there we go, the default value is salad, so we saved it, and
But for basic examples like this, we were able to take all of that code and reduce it to a simple composable. So now if we bring back that age example, how old are you, age? Well, now if we want to track this, all I have to do is say let age equals useStorage, and our key is age, and we'll have no default values in this case. All right, come back, and there they are. Ooh, age is being set to undefined. Yes, because value by default should be null. You don't have to give it to us. Okay, so let's empty those out, give it one more shot.
You don't have to give it to us. Okay, so let's empty those out, give it one more shot. And ooh, another mistake. What have I done wrong here? value is null, so that's null. So we run this, we write value equals null, or we might need to check both, because I don't want to make this a Boolean. So something like that, I don't know, we might want to come back to that. But nonetheless, I think that would fix it. So we're just handling the use cases where we should clear it from storage.
Now, a quick little bit of cleanup. We could just say write like this. And then what else can we do to clean this up? We have a function write. Let's also set up one to read, and that will simply wrap this call here. Okay, so I can say storedValue equals read from storage. If we have anything there, then let's create a ref, watch it for changes, and when it does change, let's write to localStorage. Okay, what else? Yeah, for serialization, if you're dealing with objects and
But for basic things, it won't be a problem. But yeah, deep just means go deep into that object and see if any of the child properties change. And if they do, I want to call this write function so that we can update localStorage. And just to show that to you, if we open up storage, there we go. So we have saved this object. But now let's see what happens if we change it. So we'll say let object, and then we'll do a simple setTimeout where I say, after three seconds, then set the one property equal to changed.
