-
Notifications
You must be signed in to change notification settings - Fork 3.4k
cookbook entry for storage #1550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
6735d36
6830eb5
3c67c43
11f1cdf
3662daa
678d069
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
--- | ||
title: Client-Side Storage | ||
type: cookbook | ||
order: 10 | ||
--- | ||
|
||
## Base Example | ||
|
||
Client-side storage is an excellent way to quickly add performance gains to an application. By storing data on the browser itself, you can skip fetching information from the server every time the user needs it. While especially useful when offline, even online users will benefit from using data locally versus a remote server. Client-side storage can be done with [cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies), [Local Storage](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API) (technically "Web Storage"), [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API), and [WebSQL](https://www.w3.org/TR/webdatabase/) (a deprecated method that should not be used in new projects). | ||
|
||
In this cookbook entry we'll focus on Local Storage, the simplest of the storage mechanisms. Local Storage uses a key/value system for storing data. It is limited to storing only simple values but complex data can be stored if you are willing to encode and decode the values with JSON. In general, Local Storage is appropriate for smaller sets of data you would want to persist, things like user preferences or form data. Larger data with more complex storage needs would be better stored typically in IndexedDB. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure everyone would consider an array complex data, so it might be better to be more specific here- "It would be better to store data structures such as objects or arrays in something like IndexedDB" for example |
||
|
||
Let's begin with a simple form based example: | ||
|
||
``` html | ||
<div id="app"> | ||
My name is <input v-model="name"> | ||
</div> | ||
``` | ||
|
||
This example has one form field bound to a Vue value called `name`. Here's the JavaScript: | ||
|
||
``` js | ||
const app = new Vue({ | ||
el:'#app', | ||
data:{ | ||
name:'' | ||
}, | ||
mounted() { | ||
if(localStorage.name) this.name = localStorage.name; | ||
}, | ||
watch:{ | ||
name(newName) { | ||
localStorage.name = newName; | ||
} | ||
} | ||
}); | ||
``` | ||
|
||
Focus on the `mounted` and `watch` parts. We use `mounted` to handle loading the value from local storage. To handle writing the data base, we simply watch the `name` value and on change, immediately write it. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. local storage should probably be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we also use a space after the colon in the docs, i.e. |
||
|
||
You can run this yourself here: | ||
|
||
<p data-height="265" data-theme-id="0" data-slug-hash="KodaKb" data-default-tab="js,result" data-user="cfjedimaster" data-embed-version="2" data-pen-title="testing localstorage" class="codepen">See the Pen <a href="https://codepen.io/cfjedimaster/pen/KodaKb/">testing localstorage</a> by Raymond Camden (<a href="https://codepen.io/cfjedimaster">@cfjedimaster</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://static.codepen.io/assets/embed/ei.js"></script> | ||
|
||
Type something in the form and then reload this page. You'll note that the value you typed previously will show up automatically. Don't forget that your browser provides excellent developer tools for inspecting client-side storage. | ||
|
||
 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think it would be worth showing them for Chrome as well? Many developers use Chrome so it may make the post more valuable to them. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was very intentionally trying not to use Chrome as I feel like Firefox needs more attention. :) I can absolutely add another screen shot, but to me, it feels like overkill. I will do as you ask though if you feel strongly about this. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess Firefox can use the attention but Chrome is more useful to people reading this. I'm not sure our job is to force adoption in browsers, it's rather our job to make it easy for people to work with the content. I'm not going to push you on it, it's fine as is. But the idea about writing technical content in the first place, IMO, is meeting people where they are to help them learn. If I was reading through this, I would then have to go research how to do this in Chrome. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I agree completely - but it is will also take me a grand total of ten seconds or so to include a shot of Chrome too. And heck, I can do Edge too (although I need to see if they finally added support for storage inspection.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice :) thank you |
||
|
||
Immediately writing the value may not advisable. Let's consider a slightly more advanced example. First, the updated form. | ||
|
||
``` html | ||
<div id="app"> | ||
My name is <input v-model="name"> | ||
and I am <input v-model="age"> years old. | ||
<p/> | ||
<button @click="persist">Save</button> | ||
</div> | ||
``` | ||
|
||
Now we've got two fields (again, bound to a Vue instance) but now there is the addition of a button that runs a `persist` method. Let's look at the JavaScript. | ||
|
||
``` js | ||
const app = new Vue({ | ||
el:'#app', | ||
data:{ | ||
name:'', | ||
age:0 | ||
}, | ||
mounted() { | ||
if(localStorage.name) this.name = localStorage.name; | ||
if(localStorage.age) this.age = localStorage.age; | ||
}, | ||
methods:{ | ||
persist() { | ||
localStorage.name = this.name; | ||
localStorage.age = this.age; | ||
console.log('now pretend I did more stuff...'); | ||
} | ||
} | ||
}) | ||
``` | ||
|
||
As before, `mounted` is used to load persisted data, if it exists. This time though data is only persisted when the button is clicked. This would also be where you could do any validations or transformations before storing the value. You could also store a date representing when the values were stored. With that metadata, the `mounted` method could make a logical call on whether it makes to restore the values. You can try this version below. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there's a missing comma: This time, though, data is only... The second sentence is a bit awkward, how about "We could also do any validations or transformations here before storing the value." Also: "With that metadata, the |
||
|
||
<p data-height="265" data-theme-id="0" data-slug-hash="rdOjLN" data-default-tab="js,result" data-user="cfjedimaster" data-embed-version="2" data-pen-title="testing localstorage 2" class="codepen">See the Pen <a href="https://codepen.io/cfjedimaster/pen/rdOjLN/">testing localstorage 2</a> by Raymond Camden (<a href="https://codepen.io/cfjedimaster">@cfjedimaster</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://static.codepen.io/assets/embed/ei.js"></script> | ||
|
||
## Working with Complex Values | ||
|
||
As mentioned above, Local Storage only works with simple values. To store more complex values, like arrays, you must serialize and deserialize the values with JSON. Here is a more advanced example that persists an array of cats (the best kind of array possible). | ||
|
||
``` html | ||
<div id="app"> | ||
<h2>Cats</h2> | ||
<div v-for="(cat,n) in cats"> | ||
<p> | ||
<span class="cat">{{cat}}</span> <button @click="removeCat(n)">Remove</button> | ||
</p> | ||
</div> | ||
|
||
<p> | ||
<input v-model="newCat"> | ||
<button @click="addCat">Add Cat</button> | ||
</p> | ||
|
||
</div> | ||
``` | ||
|
||
This "app" consists of a simple list on top (with a button to remove a cat) and a small form at the bottom to add a new cat. Now let's look at the JavaScript. | ||
|
||
``` js | ||
const app = new Vue({ | ||
el:'#app', | ||
data:{ | ||
cats:[], | ||
newCat:null | ||
}, | ||
mounted() { | ||
|
||
if(localStorage.getItem('cats')) { | ||
try { | ||
this.cats = JSON.parse(localStorage.getItem('cats')); | ||
} catch(e) { | ||
localStorage.removeItem('cats'); | ||
} | ||
} | ||
}, | ||
methods:{ | ||
addCat() { | ||
// ensure they actually typed something | ||
if(!this.newCat) return; | ||
this.cats.push(this.newCat); | ||
this.newCat = ''; | ||
this.saveCats(); | ||
}, | ||
removeCat(x) { | ||
this.cats.splice(x,1); | ||
this.saveCats(); | ||
}, | ||
saveCats() { | ||
let parsed = JSON.stringify(this.cats); | ||
localStorage.setItem('cats', parsed); | ||
} | ||
} | ||
}) | ||
``` | ||
|
||
In this application, we've switched to use the Local Storage APIs versus "direct" access. Both work but the API method is generally preferred. `mounted` now has to grab the value and parse the JSON value. If anything goes wrong here we assume the data is corrupt and delete it. (Remember, any time your web application uses client-side storage, the user has access to it and can modify it at will.) | ||
|
||
We have three methods now to handle working with cat. Both `addCat` and `removeCat` handle updating the "live" Vue data stored in `this.cats`. They then run `saveCats` which handles serializing and persisting the data. You can play with this version below: | ||
|
||
<p data-height="265" data-theme-id="0" data-slug-hash="qoYbyW" data-default-tab="js,result" data-user="cfjedimaster" data-embed-version="2" data-pen-title="localstorage, complex" class="codepen">See the Pen <a href="https://codepen.io/cfjedimaster/pen/qoYbyW/">localstorage, complex</a> by Raymond Camden (<a href="https://codepen.io/cfjedimaster">@cfjedimaster</a>) on <a href="https://codepen.io">CodePen</a>.</p> | ||
<script async src="https://static.codepen.io/assets/embed/ei.js"></script> | ||
|
||
## Alternative Patterns | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like this alternative patterns section but it might be nice to have a wrapping up section. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Query - would that go before or after the Alternative Patterns? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Wrapping up" is just a sentence or two at the end, after the Alternative Patterns section |
||
|
||
While the Local Storage API is relatively simple, it is missing some basic features that would be useful in many applications. The following plugins wrap Local Storage access and make it easier to use, while also adding functionality like default values. | ||
|
||
* [vue-local-storage](https://github.com/pinguinjkeke/vue-local-storage) | ||
* [vue-reactive-storage](https://github.com/ropbla9/vue-reactive-storage) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great job linking off to further documentation 👍