Categories
Vue 3

Vuex 4 — Actions

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.

Actions

In Vuex 4, actions are similar to mutations.

The differences are that actions commit mutations and actions can have async operations.

To create an action, we can add an actions property to our store.

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="increment">increment</button>  
      <p>{{count}}</p>  
    </div>  
    <script>  
      const store = new Vuex.Store({  
        state: {  
          count: 0  
        },  
        mutations: {  
          increment(state) {  
            state.count++;  
          }  
        },  
        actions: {  
          increment(context) {  
            context.commit("increment");  
          }  
        }  
      });  
      const app = Vue.createApp({  
        methods: {  
          increment() {  
            this.$store.dispatch("increment");  
          }  
        },  
        computed: {  
          count() {  
            return this.$store.state.count;  
          }  
        }  
      });  
      app.use(store);  
      app.mount("#app");  
    </script>  
  </body>  
</html>

We created a Vuex store with an actions property that has the increment method in it.

It takes the comtext parameter with the commit method to commit mutations.

To dispatch the action in our component, we call this.$store.dispatch with the action name.

We can perform async operations in an action.

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="increment">increment</button>  
      <p>{{count}}</p>  
    </div>  
    <script>  
      const store = new Vuex.Store({  
        state: {  
          count: 0  
        },  
        mutations: {  
          increment(state) {  
            state.count++;  
          }  
        },  
        actions: {  
          incrementAsync({ commit }) {  
            setTimeout(() => {  
              commit("increment");  
            }, 1000);  
          }  
        }  
      });  
      const app = Vue.createApp({  
        methods: {  
          increment() {  
            this.$store.dispatch("incrementAsync");  
          }  
        },  
        computed: {  
          count() {  
            return this.$store.state.count;  
          }  
        }  
      });  
      app.use(store);  
      app.mount("#app");  
    </script>  
  </body>  
</html>

We called commit inside the setTimeout callback to delay the action dispatch by a second.

Also, we can dispatch actions with a payload.

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="increment">increment</button>  
      <p>{{count}}</p>  
    </div>  
    <script>  
      const store = new Vuex.Store({  
        state: {  
          count: 0  
        },  
        mutations: {  
          increment(state, payload) {  
            state.count += payload.amount;  
          }  
        },  
        actions: {  
          incrementAsync({ commit }, payload) {  
            setTimeout(() => {  
              commit("increment", payload);  
            }, 1000);  
          }  
        }  
      });  
      const app = Vue.createApp({  
        methods: {  
          increment() {  
            this.$store.dispatch("incrementAsync", { amount: 2 });  
          }  
        },  
        computed: {  
          count() {  
            return this.$store.state.count;  
          }  
        }  
      });  
      app.use(store);  
      app.mount("#app");  
    </script>  
  </body>  
</html>

We added a payload parameter to the incrementAsync action to accept payloads in our action.

Then we pass in an object as the 2nd argument to the this.$store.dispatch method to dispatch our action.

We can also pass in an object with the type property and arbitrary properties that will be included in the payload:

<!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, payload) {  
            state.count += payload.amount;  
          }  
        },  
        actions: {  
          incrementAsync({ commit }, payload) {  
            setTimeout(() => {  
              commit("increment", payload);  
            }, 1000);  
          }  
        }  
      });  
      const app = Vue.createApp({  
        methods: {  
          increment() {  
            this.$store.dispatch({ type: "incrementAsync", amount: 2 });  
          }  
        },  
        computed: {  
          count() {  
            return this.$store.state.count;  
          }  
        }  
      });  
      app.use(store);  
      app.mount("#app");  
    </script>  
  </body>  
</html>

The type is the name of the action and the other properties will end up in the payload object in the 2nd argument of the incrementAsync action.

Conclusion

We can create actions to commit mutations and run async code with Vuex 4.

Categories
Vue 3

Vuex 4 — 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.

Object-Style Commit

We can pass in an object to the commit 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/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, payload) {
            state.count -= payload.amount;
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          decrement() {
            this.$store.commit({
              type: "decrement",
              amount: 5
            });
          }
        },
        computed: {
          count() {
            return this.$store.state.count;
          }
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We pass in an object into the this.$store.commit method.

Properties other than type would end up in the payload object.

So we can access the amount property with payload.amount in the decrement mutation method.

Mutations follow Vue 3’s reactivity rules, so whenever a reactive state variable is updated, the store state will change.

Using Constants for Mutation Types

We can use constants for mutation types.

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 DECREMENT = "decrement";

      const store = new Vuex.Store({
        state: {
          count: 0
        },
        mutations: {
          [DECREMENT](state) {
            state.count -= 1;
          }
        }
      });
      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 passed in a DECREMENT constant which we passed in as the name of the mutation in the mutations property and the commit method.

It’s useful for reusing the same name in multiple places.

Mutations Must Be Synchronous

Mutations must be synchronous.

This is because the code inside it must run in sequence so that we can trace the code.

Committing Mutations in Components

We can call the mapMutations method to map our mutations to a 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/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 -= 1;
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapMutations(["decrement"])
        },
        computed: {
          count() {
            return this.$store.state.count;
          }
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

to call the Vuex.mapMutations method to a method.

Then we can run that when we click the decrement button.

Payloads are supported when we map mutations with mapMutations .

We can map a mutation to a method with a different name with:

<!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="subtract">decrement</button>
      <p>{{count}}</p>
    </div>
    <script>
      const store = new Vuex.Store({
        state: {
          count: 0
        },
        mutations: {
          decrement(state) {
            state.count -= 1;
          }
        }
      });
      const app = Vue.createApp({
        methods: {
          ...Vuex.mapMutations({
            subtract: "decrement"
          })
        },
        computed: {
          count() {
            return this.$store.state.count;
          }
        }
      });
      app.use(store);
      app.mount("#app");
    </script>
  </body>
</html>

We mapped the decrement mutation to the subtract method with the key of the object we passed into mapMutations .

Conclusion

We can create and mutations in various to change Vuex state data.

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.