Categories
JavaScript Vue

Saving Data to Local Storage in a Vue App with Vue-LS

Saving data with the standard JavaScript localStorage object in a Vue app is a pain because it has limited features.

To make our lives easier, we can use a library to make our lives easier.

In this article, we’ll look at how to use the vue-ls library to save data to local storage the Vue way.

Getting Started

We can use the vue-ls library to lets us manipulate the local storage of a browser in an easy way.

To install it, we can run:

npm i vue-ls

Then to use it, we first register the plugin:

import Vue from "vue";
import App from "./App.vue";
import Storage from "vue-ls";

const options = {
  namespace: "vuejs__",
  name: "ls",
  storage: "local"
};

Vue.use(Storage, options);
Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

We called Vue.use with an options object.

The namespace is the prefix to add the key.

The name is the property name to access the vue-ls methods.

storage is the kind of storage to save the data in. It can be local, storage or memory.

Saving Data

To save data in a component, we can write:

<template>
  <div id="app"></div>
</template>

<script>
export default {
  name: "App",
  mounted() {
    this.$ls.set("foo", "boo", 60 * 60 * 1000);
  }
};
</script>

We called this.$ls.set to save an item with the key vuejs__foo since we prefixed the key withe vuejs__ according to the namespace prop.

The second argument is the key, the 3rd is the expiry time.

The last 2 arguments will be saved as a stringified object.

The key would be vuejs__foo and the value is:

{"value":"boo","expire":1589754925116}

We can check against the expiry timestamp and do what we want with it if it expires.

Adding an expiry time is a feature that isn’t available with local storage.

So, this is a valuable feature.

Watching for Changes

We can also add listen for changes in data with:

<template>
  <div id="app"></div>
</template>

<script>
export default {
  name: "App",
  mounted() {
    const callback = (val, oldVal, uri) => {
      console.log(val, oldVal, uri);
    };

    this.$ls.on("foo", callback);
    this.$ls.set("foo", "boo", 60 * 60 * 1000);
  }
};
</script>

We have the this.$ls.on method. The first argument is the key without the prefix.

The callback it a callback when the local storage item with vuejs__foo is updated.

It has the val parameter with the current value. oldVal has the current value.

uri is the URL that the app is hosted on.

We watch the value with this call.

To unwatch the item, we can write:

this.$ls.off('foo', callback)

Where callback is the same callback we passed into the $on method.

Getting Data

To get a local storage item value by its key, we can write:

this.$ls.get('foo', 10);

Then we get the local storage with the key foo. If the value isn’t found, then 10 is returned.

Finally, to remove a value, we can write:

this.$ls.remove('foo');

to remove the value with the key vuejs__foo assuming the namespace is set to vuejs__.

Conclusion

vue-ls is an easy to use local storage library that lets us do things like adding an expiry timestamp to the saved data and watch for changes to values in addition to what’s already available in browser’s localStorage object.

Categories
JavaScript Vue

Making HTTP Requests in a Vue App with Axios

Most apps need to make HTTP requests to an external server for them to be useful.

Vue.js doesn’t come with its own HTTP client.

However, we can add one ourselves so that we can make requests with it.

One of the best HTTP client libraries is the Axios HTTP client.

To use it, first, we in install it by running:

npm i axios

Then to make requests with it, we can write:

<template>
  <div id="app">{{info}}</div>
</template>

<script>
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      info: {}
    };
  },
  mounted() {
    axios
      .get("https://api.coindesk.com/v1/bpi/currentprice.json")
      .then(response => (this.info = response.data.bpi));
  }
};
</script>

We make a request to the Conidesk API to get the current prices of Bitcoins.

To make the code shorter, we can write:

<template>
  <div id="app">{{info}}</div>
</template>

<script>
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      info: {}
    };
  },
  async mounted() {
    const response = await axios.get(
      "https://api.coindesk.com/v1/bpi/currentprice.json"
    );
    this.info = response.data.bpi;
  }
};
</script>

We replaced then with await to make the code shorter.

To deal with errors, we can use try/catch:

<template>
  <div id="app">
    <div v-if="!errored">{{info}}</div>
    <div v-else>error</div>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      info: {},
      errored: false
    };
  },
  async mounted() {
    try {
      const response = await axios.get(
        "https://api.coindesk.com/v1/bpi/currentprice.json"
      );
      this.info = response.data.bpi;
    } catch (error) {
      this.errored = true;
    }
  }
};
</script>

We set this.errored to true in the catch block. This catches the error.

Now we see the price data from the API.

To format the data, we can rewrite our template to display the price.

We can write:

<template>
  <div id="app">
    <div v-if="!errored">
      <p v-for="(value, key) in info" :key="key">{{key}}: {{value.rate}}</p>
    </div>
    <div v-else>error</div>
  </div>
</template>

<script>
import axios from "axios";

export default {
  name: "App",
  data() {
    return {
      info: {},
      errored: false
    };
  },
  async mounted() {
    try {
      const response = await axios.get(
        "https://api.coindesk.com/v1/bpi/currentprice.json"
      );
      this.info = response.data.bpi;
    } catch (error) {
      this.errored = true;
    }
  }
};
</script>

We have the v-for directive to loop through the info object.

The value has the value of a property.

The key has the key, which is the currency code.

We display the code property of the value to display the price.

With Axios, we can make HTTP requests easily within in a Vue.js component.

Categories
JavaScript Vue

Add a Table to a Vue App with Buefy

We can add a table with Buefy with the b-table component.

Basic Table

To populate it with data, we just set the data and columns props.

For example, we write:

<template>
  <div id="app">
    <b-table :data="data" :columns="columns"></b-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      data: [
        { id: 1, firstName: "jane", lastName: "smith" },
        { id: 2, firstName: "joe", lastName: "smith" },
        { id: 3, firstName: "alex", lastName: "green" }
      ],
      columns: [
        {
          field: "id",
          label: "ID",
          width: "40",
          numeric: true
        },
        {
          field: "firstName",
          label: "First Name"
        },
        {
          field: "lastName",
          label: "Last Name"
        }
      ]
    };
  }
};
</script>

This creates a basic table by using the b-table component.

We set the data prop to data, which has an array of objects.

Each property is mapped to the column by specifying the objects in columns.

field is the property name to display in the column.

label is the table heading to display in the column.

width is the width of the column.

numeric indicates whether the column is numeric.

The columns are displayed in the way they’re listed in the columns array.

So ID is first, First Name is second, and Last Name is third.

Custom Table

We can add other styles.

To make columns customizable, we can populate slots for the content.

We can use the b-table-column to do that:

<template>
  <div id="app">
    <b-table :data="data" hoverable>
      <template slot-scope="props">
        <b-table-column field="id" label="ID" width="40" numeric>{{ props.row.id }}</b-table-column>

        <b-table-column field="firstName" label="First Name">{{ props.row.firstName }}</b-table-column>

        <b-table-column field="lastLame" label="Last Name">{{ props.row.lastName }}</b-table-column>
      </template>

      <template slot="empty">
        <section class="section">
          <div class="has-text-centered">
            <p>Nothing here.</p>
          </div>
        </section>
      </template>
    </b-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      data: [
        { id: 1, firstName: "jane", lastName: "smith" },
        { id: 2, firstName: "joe", lastName: "smith" },
        { id: 3, firstName: "alex", lastName: "green" }
      ],
      columns: [
        {
          field: "id",
          label: "ID",
          width: "40",
          numeric: true
        },
        {
          field: "firstName",
          label: "First Name"
        },
        {
          field: "lastName",
          label: "Last Name"
        }
      ]
    };
  }
};
</script>

We filled the default slot with b-table-column components.

The props variable has the table data.

We access the with props.row as we did in the code.

The empty slot lets us show something when there’s no data to show in the table.

We replaced the columns prop with the slots.

We can add the bordered, striped, narrowed, hoverable, loading, focusable, and mobile-cards props to set the table options.

mobile-cards means the rows appear as cards on mobile.

The rest is self-explanatory.

In the example above, we added hoverable to b-table to make the rows highlight when hovered.

With Buefy, we can create a table, with the b-table component.

We can either pass in the columns prop to set the columns, or we can use slots to fill the content.

There are also other customization options.

Categories
JavaScript Vue

Add a Dropdown to a Vue App vue-select

We can add a dropdown easily with the vue-select package.

First, we install the package by running:

npm install vue-select

Then we register the plugin by writing:

import Vue from "vue";
import App from "./App.vue";
import vSelect from "vue-select";
import 'vue-select/dist/vue-select.css';

Vue.component("v-select", vSelect);
Vue.config.productionTip = false;

new Vue({
  render: h => h(App)
}).$mount("#app");

We register the component with:

Vue.component("v-select", vSelect);

and added the required CSS with:

import 'vue-select/dist/vue-select.css';

Then we can create a basic drop down in our component by writing:

<template>
  <div id="app">
    <v-select :options="['apple', 'orange']"></v-select>
  </div>
</template>

<script>
export default {};
</script>

We passed in an array of strings, which will be the display text and the value.

Also, we can pass in an array of objects:

<template>
  <div id="app">
    <v-select label="text" :options="options"></v-select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: [
        { value: "apple", text: "Apple" },
        { value: "orange", text: "Orange" }
      ]
    };
  }
};
</script>

We have the options array, and we set the label prop to choose the property to display to the user.

We set it to text, so we display the value of the text property of each entry to the user.

To get the value selected, we can bind the selected value to a state with the v-model directive:

<template>
  <div id="app">
    <v-select label="text" v-model="selected" :options="options"></v-select>
    <p>{{selected}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: [
        { value: "apple", text: "Apple" },
        { value: "orange", text: "Orange" }
      ],
      selected: ""
    };
  },
};
</script>

Now we’ll get the object selected if we pick one from the list.

To assign the value property’s value to selected, we can pass in a function to the reduce prop:

<template>
  <div id="app">
    <v-select label="text" :reduce="reduce" v-model="selected" :options="options"></v-select>
    <p>{{selected}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: [
        { value: "apple", text: "Apple" },
        { value: "orange", text: "Orange" }
      ],
      selected: ""
    };
  },
  methods: {
    reduce(fruit) {
      return fruit.value;
    }
  }
};
</script>

Now the value of value is set as the value of selected when we pick our choice.

To add a placeholder, we can use the placeholder prop:

<template>
  <div id="app">
    <v-select
      label="text"
      placeholder="choose a fruit"
      :reduce="reduce"
      v-model="selected"
      :options="options"
    ></v-select>
    <p>{{selected}}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      options: [
        { value: "apple", text: "Apple" },
        { value: "orange", text: "Orange" }
      ],
      selected: ""
    };
  },
  methods: {
    reduce(fruit) {
      return fruit.value;
    }
  }
};
</script>

Now we’ll see ‘choose a fruit’ before we make a choice.

When we click ‘x’ to clear the selection, we’ll see the placeholder again.

We can use the vue-select package to add a customizable drop-down easily.

It can bind to states with v-model and add a placeholder with the placeholder prop.

Categories
JavaScript Vue

Basic Vuex Store Example

Vuex is one state management that’s made to work with Vue apps.

To use it, we install it by running:

npm i vuex

Then we can register the Vuex plugin and create a basic Vuex store with:

import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  }
});

Vue.config.productionTip = false;

new Vue({
  store,
  render: h => h(App)
}).$mount("#app");

The store is created with the Vuex.Store constructor:"

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  }
});

The object we passed into the constructor has the state property with the count state inside it.

The initial value of count is 0.

The mutations property has methods that change the state.

We have the increment method which takes the state parameter.

It has the same content as the state property above it.

So we can run state.count++ to increment the value of the count state.

To make the store available to the rest of our app, we put store into the object we passed into the Vue constructor.

Then in a component, we can use the store as follows:

<template>
  <div id="app">
    <button @click="increment">Increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count;
    }
  },
  methods: {
    increment() {
      this.$store.commit("increment");
    }
  }
};
</script>

We have this.$store, which is the Vuex store. We have this object since we registered the Vuex plugin and put the store into the object we passed into the Vue constructor.

this.$store.state.count has the count state’s value.

And this.$store.commit lets us commit values to the store.

The 'increment' indicates that we call the increment method in the mutations property in the object we pass into the Vuex.Store constructor.

We also have an Increment button to call increment.

Now when we click the button, we’ll see the count value displayed below the button go up.

The commit method takes a 2nd argument, which is the payload.

So we can write:

import Vue from "vue";
import App from "./App.vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state, payload) {
      state.count += payload;
    }
  }
});

Vue.config.productionTip = false;

new Vue({
  store,
  render: h => h(App)
}).$mount("#app");

and write:

<template>
  <div id="app">
    <button @click="increment">Increment</button>
    <p>{{count}}</p>
  </div>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count;
    }
  },
  methods: {
    increment() {
      this.$store.commit("increment", 2);
    }
  }
};
</script>

to increment by 2 instead of 1 when we click.

We have:

this.$store.commit("increment", 2);

instead of:

this.$store.commit("increment");

and:

mutations: {
  increment(state, payload) {
   state.count += payload;
  }
}

instead of:

mutations: {
  increment(state,) {
    state.count ++;
  }
}

payload is the 2 we passed in.

A basic Vuex store can let us store an app-wide state easily.

We just have to commit mutations to set the state and get the state from the store via the $this.store.state property.

We can return the latest value in the computed property so we can use it.