Categories
Vue 3

Vuex 4 — Getters and Mutations

Vuex 4 is in beta and it’s subject to change.

Vuex is a popular state management library for Vue.

Vuex 4 is the version that’s made to work with Vue 3.

In this article, we’ll look at how to use Vuex 4 with Vue 3.

Method-Style Getters

We can create getters that take one or more arguments.

To do that, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <div>
        <p>{{getTodoById(1).text}}</p>
      </div>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          todos: [
            { id: 1, text: "eat", done: true },
            { id: 2, text: "sleep", done: false }
          ]
        },
        getters: {
          getTodoById: (state) => (id) => {
            return state.todos.find((todo) => todo.id === id);
          }
        }
      });

      const app = Vue.createApp({
        computed: {
          ...Vuex.mapGetters(["getTodoById"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We created a store with the todos state.

And we have the getTodosById getter method which returns a function that takes the id parameter and returns the state.todos entry that matches the given id value.

In the root Vue instance, we called Vuex.mapGetters to map the getters to methods.

This way, we can call it in our component as we did in our template.

Mutations

We can use mutations to change the states in our store.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="decrement">decrement</button>
      <p>{{count}}</p>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          count: 0
        },
        mutations: {
          decrement(state) {
            state.count--;
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          decrement() {
            this.$store.commit("decrement");
          }
        },
        computed: {
          count() {
            return this.$store.state.count;
          }
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We have the decrement mutation that reduces state.count ‘s value by 1.

Then we can call the this.$store.commit method to invoke our mutation to update the count state.

We also have a computed count property to get the value of the count state.

Commit with Payload

We can create a mutation that takes a payload.

A mutation method accepts a 2nd parameter with the value.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="decrement">decrement</button>
      <p>{{count}}</p>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          count: 0
        },
        mutations: {
          decrement(state, n) {
            state.count -= n;
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          decrement() {
            this.$store.commit("decrement", 5);
          }
        },
        computed: {
          count() {
            return this.$store.state.count;
          }
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We added a n parameter to the decrement mutation method to subtract state.count by n .

Then we call commit with a 2nd argument to pass in the value we want to subtract state.count by.

Now when we click the decrement button, we can see the count value displayed.

Conclusion

We can create mutations to modify state data with Vuex 4.

Getters can be defined as a method style getter to let us pass in arguments.

Categories
Vue 3

Getting Started with Vuex 4 with Vue 3

Vuex 4 is in beta and it’s subject to change.

Vuex is a popular state management library for Vue.

Vuex 4 is the version that’s made to work with Vue 3.

In this article, we’ll look at how to use Vuex 4 with Vue 3.

Installation

We can install it from CDN.

To do that, we add the script tag for Vuex.

Then we can use that to create the app by writing:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <button @click="increment">increment</button>
      <p>{{count}}</p>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          count: 0
        },
        mutations: {
          increment(state) {
            state.count++;
          }
        }
      });

      const app = Vue.createApp({
        methods: {
          increment() {
            this.$store.commit("increment");
          }
        },
        computed: {
          count() {
            return this.$store.state.count;
          }
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We included the global version of Vuex so that we can use it with HTML script tags.

In the code above, we created a store with the Vuex.Store constructor.

The state has the store’s states.

We have the count state with the count.

mutations has the store mutations.

It has the inctrement mutation method to increment the count state.

Then in our root Vue instance, we have the increment method that calls the this.$store.commit method to increment the count state.

And we have the computed count property to return the count state from the store.

The store state is accessible with the this.$store.state property.

We call the increment method when we click on the increment button.

Then we see the count state updated and displayed in the template via the computed property.

We have th remember to add:

app.use(store);

so that the store can be used with our Vue 3 app.

The changes are reactive, so whenever the store’s state changes, we’ll get the latest changes in our components.

Getters

We can add getters to make getting states easier.

Instead of computed properties, we can use getters to get the latest value of our state.

To add a getter, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <div>
        <p v-for="d of doneTodos" :key="d.id">{{d.text}}</p>
      </div>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          todos: [
            { id: 1, text: "eat", done: true },
            { id: 2, text: "sleep", done: false }
          ]
        },
        getters: {
          doneTodos: (state) => {
            return state.todos.filter((todo) => todo.done);
          }
        }
      });

      const app = Vue.createApp({
        computed: {
          ...Vuex.mapGetters(["doneTodos"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

to create a doneTodos getter in our store and use it in our Vue instance with the Vuex.mapGetters method.

The argument is an array of strings with the name of the getters.

We spread the computed properties into the computed object so that we can access the in our component.

In the template, we render the items from the getter.

The doneTodos method has the state parameter with the state.

It can also receive other getters as a second argument.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vuex@4.0.0-beta.4/dist/vuex.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <div>
        <p v-for="d of doneTodos" :key="d.id">{{d.text}}</p>
        <p>{{doneTodosCount}} done</p>
      </div>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          todos: [
            { id: 1, text: "eat", done: true },
            { id: 2, text: "sleep", done: false }
          ]
        },
        getters: {
          doneTodos: (state) => {
            return state.todos.filter((todo) => todo.done);
          },
          doneTodosCount: (state, getters) => {
            return getters.doneTodos.length;
          }
        }
      });

      const app = Vue.createApp({
        computed: {
          ...Vuex.mapGetters(["doneTodos", "doneTodosCount"])
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We have the doneTodosCount getter with the getters object that has our getters.

We used it to access the doneTodos getter with it and used mapGetters to display to make it accessible in our component.

Then we should see the todos that have done set to true and get 1 done in our component displayed.

Conclusion

We can create a simple Vue 3 app with Vuex 4 with a simple store and getters.

Categories
Vue 3

Vue Router 4–Async Scrolling, Lazy-Loading, and Navigation Errors

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Async Scrolling Behavior

We can change the scrolling behavior to be async.

To do that, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: `<div>
          <p v-for='n in 100'>{{n}}</p>
        </div>`
      };
      const Bar = {
        template: `<div>
          <p v-for='n in 100'>{{n}}</p>
        </div>`
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes,
        scrollBehavior(to, from, savedPosition) {
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve({ left: 0, top: 500 });
            }, 500);
          });
        }
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the scrollBehavior method that returns a promise that resolves to an object with the scroll position.

The left and top properties are the new properties for the x and y coordinates.

They replace the x and y properties in Vue Router 3.

Lazy Loading Routes

We can lazy load routes in our app.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="[https://unpkg.com/vue@next](https://unpkg.com/vue@next)"></script>
    <script src="[https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js](https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js)"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = () =>
        Promise.resolve({
          template: `<div>foo</div>`
        });

      const Bar = () =>
        Promise.resolve({
          template: `<div>bar</div>`
        });

      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the Foo and Bar components.

They are defined by creating a promise that resolves to the component definition.

With Webpack, we can also use the import function to import our component.

For example, we can write:

import('./Foo.vue')

import returns a promise that resolves to the component, and it works the same way as the promise definition above.

Grouping Components in the Same Chunk

We can group components in the same chunk bu creating components with functions:

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

We have comments with webpackChunkName with the chunk name.

Navigation Failures

We can handle navigation errors with programmatic navigation.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <a href="#" [@click](http://twitter.com/click "Twitter profile for @click").stop="go">bar</a>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: `<div>foo</div>`
      };

      const Bar = {
        template: `<div>bar</div>`,
        beforeRouteEnter(to, from, next) {
          next(new Error());
        }
      };

      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({
        methods: {
          go() {
            this.$router.push("/bar").catch((failure) => {
              console.log(failure);
            });
          }
        }
      });
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

to handle navigation errors.

We a beforeRouterEnter method in the Bar component.

It calls next with an Error instance within the method.

This will cause the Error instance to be shown in the console.

In the root Vue instance, we have the go method that tries to go to the /bar route with the push method.

The catch method’s callback has the failure parameter that has the Error instance that’s thrown.

Conclusion

We can change scrolling behavior to be async.

Also, components can be grouped into chunks and lazy-loaded.

Finally, we can handle errors that are raised from programmatic navigation.

Categories
Vue 3

Vue Router 4–Scroll Behavior

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Scroll Behavior

We can change the scroll behavior with the Vue Router.

To do that, we add the scrollBehavior method to the object that we pass into the createRouter method.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
    </div>
    <script>
      const Foo = {
        template: `<div>
          <p v-for='n in 100'>{{n}}</p>
        </div>`
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes,
        scrollBehavior(to, from, savedPosition) {
          return { left: 0, top: 500 };
        }
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We return an object with the left and top properties.

left is the x coordinate and top is the y coordinate we want to scroll to when the route changes.

to has the route object of the route we’re moving to.

And from has the route object we’re moving from.

Now when we click on the router links, we move to somewhere near the top of the page.

savedPosition has the position that we scrolled in the previous route.

We can use the savedPosition object as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <router-view></router-view>
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
    </div>
    <script>
      const Foo = {
        template: `<div>
          <p v-for='n in 100'>{{n}}</p>
        </div>`
      };
      const Bar = {
        template: `<div>
          <p v-for='n in 150'>{{n}}</p>
        </div>`
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes,
        scrollBehavior(to, from, savedPosition) {
          if (savedPosition) {
            return savedPosition;
          } else {
            return { left: 0, top: 0 };
          }
        }
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

When the savedPosition object is defined, we return it.

Otherwise, we scroll to the top when we click on the router links.

Now when we scroll to the links, we’ll stay at the bottom of the page.

Conclusion

We can scroll to a given position on the page with Vue Router 4 with the scrollBehavior method.

Categories
Vue 3

Vue Router 4–Per-Route Transition and Fetch Data During Navigation

Vue Router 4 is in beta and it’s subject to change.

To build a single page app easily, we got to add routing so that URLs will be mapped to components that are rendered.

In this article, we’ll look at how to use Vue Router 4 with Vue 3.

Per-Route Transition

We can add transition effects for individual routes.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
    <style>
      .fade-enter-active,
      .fade-leave-active {
        transition: opacity 0.15s ease;
      }
      .fade-enter-from,
      .fade-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view v-slot="{ Component, route }">
        <transition :name='route.path.includes("foo") ? `fade`:``'>
          <component :is="Component" />
        </transition>
      </router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>bar</div>"
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We set the name prop dynamically by checking the route object.

The path property has the path of the route.

Therefore, we can check the path to set the value of name to the transition we want.

Data Fetching

We sometimes want to fetch data from the server when a route is activated.

This can be done with various hooks.

For instance, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar">bar</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>{{bar}}</div>",
        data() {
          return {
            bar: ""
          };
        },
        created() {
          this.fetchData();
        },
        watch: {
          $route: "fetchData"
        },
        methods: {
          async fetchData() {
            this.bar = "bar";
          }
        }
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We watch the $route object and call the fetchData method when $route changes.

Fetching Before Navigation

We can also run methods to fetch data in the before before navigation hooks.

For example, we can write:

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/vue@next"></script>
    <script src="https://unpkg.com/vue-router@4.0.0-beta.7/dist/vue-router.global.js"></script>
    <title>App</title>
  </head>
  <body>
    <div id="app">
      <p>
        <router-link to="/foo">foo</router-link>
        <router-link to="/bar/1">bar 1</router-link>
        <router-link to="/bar/2">bar 2</router-link>
      </p>
      <router-view></router-view>
    </div>
    <script>
      const Foo = {
        template: "<div>foo</div>"
      };
      const Bar = {
        template: "<div>{{id}}</div>",
        data() {
          return {
            id: ""
          };
        },
        beforeRouteEnter(to, from, next) {
          next((vm) => vm.fetchData(to.params.id));
        },
        beforeRouteUpdate(to, from, next) {
          this.fetchData(to.params.id);
          next();
        },
        methods: {
          async fetchData(id) {
            this.id = id;
          }
        }
      };
      const routes = [
        {
          path: "/foo",
          component: Foo
        },
        {
          path: "/bar/:id",
          component: Bar
        }
      ];
      const router = VueRouter.createRouter({
        history: VueRouter.createWebHistory(),
        routes
      });
      const app = Vue.createApp({});
      app.use(router);
      app.mount("#app");
    </script>
  </body>
</html>

We have the beforeRouterEnter and beforeRouteUpdate hooks that runs before the component is rendered and when the route params change respectively.

The fetchData method is run in the callback to get the data.

We have 2 router-link that goes to the /bar route with its own parameter.

We get the id parameter in the navigation guard methods and called fetchData to set its value to the this.id state.

Therefore, we’ll see them in the template.

Conclusion

We have a new way to add per-route transition with Vue Router 4.

Also, we can fetch data in Vue Router hooks with Vue Router 4.