Saving User-Generated Data
As users interact with our sites and applications, they will create data that we wish to store. Many times, we will be working with a server-based remote API that provides data, and in many cases we will store the data a user creates on our server. For example, if a user is writing blog posts, we will likely store the blog post on the server and not worry about providing a local cache of that blog post. However, there are times when we want to cache information generated by the user for quick use within our application.
Imagine an ecommerce experience where the user is adding items to a shopping cart. We might wish to provide a panel in our interface that can slide out to show the items the user has selected. Of course, we would be saving the shopping cart on the server so we could retrieve it if the user logs in from another device, but we might also want to store the shopping cart items in the browser's localStorage
to allow quick and easy access to this information.
Storing and Retrieving Data
If we are already maintaining an Array called shoppingCart
then we can easily just save that value to localStorage
and then retrieve it from localStorage
when we need it again. Assuming we use a tool such as vue-ls
to help us work with localStorage
, here is what that might look like.
First, we would need to configure the use of vue-ls
in our main.js
file where we define our Vue
instance:
import VueLocalStorage from 'vue-ls';
let options = {
namespace: 'catalog__'
};
Vue.use(VueLocalStorage, options);
We import the vue-ls
module for use in this component. Then we define an options
object, which only contains one property: namespace
. This is a name that will be prepended to any data we save in the object. In some very complex situations, it would be helpful to use this namespace to keep data properly delineated. Finally, the Vue.use(VueLocalStorage, options)
command makes vue-ls
available as this.$ls
in our components. We can use it in any of our components.
Inside a component, we could have code like this to add items from the catalog into a shopping cart.
addItem: function (item) {
if (this.shoppingCart.indexOf(item) === -1) {
this.shoppingCart.push(word);
this.$ls.set('shoppingCart', this.shoppingCart);
console.log(`Added ${item} to shoppingCart.`);
} else {
console.log('Item is already in shoppingCart.');
}
},
removeItem: function (item) {
this.shoppingCart.splice(this.shoppingCart.indexOf(item), 1);
this.$ls.set('shoppingCart', this.shoppingCart);
}
We use the get
and set
methods to work with the values in localStorage
. In the example above, we see the methods for addItem
and removeItem
are using this.$ls.set
to set the updated values in localStorage
at the same time as the value is set on the this.shoppingCart
object.
We can look at this next example to see how we can initialize the shoppingCart
object when we load a component:
created () {
if (this.$ls.get('shoppingCart')){
this.shoppingCart = this.$ls.get('shoppingCart');
} else {
this.$ls.set('shoppingCart', this.shoppingCart);
}
}
In this example we can see that we have defined a created
function on our component, which will be executed when the component is loaded. It will try to get the shoppingCart
value from localStorage
. If there is no shoppingCart
value in localStorage
, then the this.$ls
module will return undefined
, which evaluates to false
. So if there is no shoppingCart
value in localStorage
, then we initialize that value to the this.shoppingCart
value. If there is a value for shoppingCart
in localStorage
, then we use that value as our starting point.
The vue-ls
module handles converting any data objects into Strings and then rendering them back into data objects when we retrieve them. This saves us several repetitive lines we would need to use whenever we saved/retrieved items from storage, and it allows us to have very simple statements to handle the get
and set
of data.
Passing Values Between Components
In Vue.js we have a one-way data flow: Properties are passed from parent components to child components. Child components can emit an event trigger, but they (normally) cannot sync data between values in the parent and child components. This setup seems clunky at first, but once we have a means of storing data in a single place that is accessible to all of the components in our application, we have an easier time.
We can imagine all kinds of situations where components could modify the same value in localStorage
and then just emit the signal that the localStorage
value has changed. This would prompt parent components to re-render their lists and allow everything to stay in sync across the system.
This can also be very helpful in other situations where, for example, we might have a component that handles the presentation of site "preferences" to the user. These preferences could be written into storage of some kind and then read by the other components that would use those preferences. For example, a user could select to always see english subtitles on videos, and the component responsible for embedding videos could check that preference value and properly modulate the display of the video for the user.
Now that we have a readily available and easy way of passing data between parts of our application, we can explore all kinds of interface and workflow possibilities. We should never feel compelled to cram a bunch of features into a single component because we cannot figure out how to share data properly between more targeted components. We could improve several previous projects we've seen in this book by using these techniques to refactor and break apart large components. (Take a look back at the WordSearch
project in Section 12 for a good example of how this technique could allow us to have a nicer organization within that project.)