Cannot get Vuex state on async created - vue.js

I have the following Vue component (based on the HelloWorld example)
<template>
<div class="hello">
<h1>Message: {{ post.title }} </h1>
<button #click="refreshPost">Refresh</button>
</div>
</template>
<script lang="ts">
import {Component, Vue, Prop, } from 'vue-property-decorator';
import {IPostModel} from "#/models/IPostModel";
import {namespace} from "vuex-class";
const postStore = namespace('Post')
#Component
export default class HelloWorld extends Vue {
#postStore.State
public post!: IPostModel
#postStore.Action
public refreshPost!: Promise<IPostModel>
async created(){
console.log("starting created");
await this.refreshPost;
console.log("finished created");
}
}
</script>
And the corresponding Vuex module
import axios from 'axios'
import { VuexModule, Module, Mutation, Action } from "vuex-module-decorators";
import { IPostModel } from "#/models/IPostModel";
#Module({namespaced: true })
class Post extends VuexModule{
public post: IPostModel = { id: -1, userId: -1, title: ''}
#Mutation
private setPost(newPost: IPostModel): void{
this.post = newPost;
}
#Action({rawError: true})
public async refreshPost(): Promise<IPostModel> {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
this.context.commit('setPost', response.data);
return response.data;
}
}
export default Post
The code to call the api works fine when I click the refresh button, but doesn't appear to set the state on the created hook and I can't figure out why. I've tried various combinations of Promise & await & async and it either errors or makes no difference.

You are missing some steps. Let's call the Vuex module, VuexModule
In your VuexModule, add a namespace name
#Module({ namespaced: true, name: 'Post' }) // add name
import { namespace } from 'vuex-class';
import { getModule } from 'vuex-module-decorators';
import VuexModule from '#/store/modules/vuexmodule.ts'; //module which has 'Post' as namespace
const postStore = namespace('Post')
let vuexModule: VuexModule;
export default class HelloWorld extends Vue {
async created() {
vuexModule = getModule(VuexModule, this.$store);
await vuexModule.refreshPost();
}
}
You can learn more in this excellent article

Related

How to use $store.commit in Nuxt with #vue/composition-api

<template>
<div>
<h1>Vuex Typescript Test</h1>
<button #click="handleLogin">click</button>
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/composition-api'
export default defineComponent({
setup() {
return {
handleLogin() {
// something....
},
}
},
})
</script>
#vue/composition-api do not apply useStore
I want to use store in setup function.
You should be able to access the useStore composable in the setup function according to the documentation of Vuex.
Your script section will look like this:
import { defineComponent } from '#vue/composition-api';
import { useStore } from 'vuex';
export default defineComponent({
setup() {
return {
const store = useStore();
return {
handleLogin {
store.dispatch('auth/login');
},
};
}
},
});
The proper way to structure the content of setup would be to move the handleLogin as a separate const and expose the constant in the return, in order to keep the return section more readable like this:
setup() {
const store = useStore();
const handleLogin = () => {
store.dispatch('auth/login');
};
return {
handleLogin,
}
}

vuex module axios in vue3 composition api

I am new to vue and working with vue 3 and the composition API. It is really hard to find stuff related to the composition API, so I try to get help here. I am using axios in combination with vuex modules for consuming APIs.
How can I transfere this code from options API to composition API?
TestApi.js
import axios from 'axios'
const posts = {
namespaced: true,
state: {
posts: []
},
mutations: {
SET_POSTS(state, data){
state.posts = data
}
},
actions: {
loadPosts({commit}) {
axios
.get('https://jsonplaceholder.typicode.com/posts')
.then( res => {
commit('SET_POSTS', res.data)
} )
.catch(error => console.log(error))
}
},
getters: {
getPosts(state){
return state.posts
}
}
}
export default posts
App.vue
<template>
<div class="post" v-for="post in getPosts" :key="post.id">
<h1>{{ post.title }}</h1>
<p>{{ post.body }}</p>
</div>
</template>
<script>
import { mapGetters } from 'vuex'
import { computed } from 'vue'
export default {
computed: {
...mapGetters('posts', ['getPosts'])
},
created(){
this.$store.dispatch('posts/loadPosts')
}
}
</script>
<style>
.post{
margin-bottom: 20px;
}
</style>
I tried something like this. But it does not work:
import { useStore } from 'vuex'
import { computed } from 'vue'
export default {
setup(){
getData();
const store = useStore()
function getData(){
store.dispatch('posts/loadPosts');
}
const getPosts = computed(() => store.getters['getPosts'])
return{ getPosts }
}
}
Error: Uncaught (in promise) TypeError: Cannot read property 'dispatch' of undefined
You shoul run the function after initializing the store :
setup(){
const store = useStore()
getData();
...

Refresh my Vue Apollo query when params change

I am new to vue, but I don't understand why no one seems to be able to answer this question. I would have thought it was simple, but I can't find anywhere talking about it.
I have this method getCategory that uses Apollo to query our graphQL API.
The logic looks like this:
import { useQuery, useResult } from "#vue/apollo-composable";
import * as getCategoryBySlug from "#graphql/api/query.category.gql";
export function useGetCategory(slug: string) {
const { result, loading, error } = useQuery(getCategoryBySlug, { slug });
const category = useResult(result, null, (data) => data.categoryBySlug);
return { category, loading, error };
}
When I want to use this in a component, I can simply do this:
import { computed, defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
import { defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
export default defineComponent({
name: "Categories",
components: { CategoryTitle, Products },
setup(_, context) {
const { category, loading, error } = useGetCategory(
context.root.$route.params.slug
);
return { category, loading, error };
},
});
And that's fine. Then in my template, I can do what I need to do like this:
<template>
<div>
<category-title v-if="category" :category="category"> </category-title>
<base-loader :loading="loading"> </base-loader>
<products :category="category" v-if="category"></products>
</div>
</template>
<script src="./category.component.ts" lang="ts"></script>
<style src="./category.component.scss" lang="scss" scoped></style>
Now comes the issue (which in my mind, should be dead easy). I need to handle route changes, specifically the slug.
So I have changed my code to this:
import { computed, defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
export default defineComponent({
name: "Categories",
components: { CategoryTitle, Products },
setup(_, context) {
const result = computed(() => {
return useGetCategory(context.root.$route.params.slug);
});
return { result };
},
});
which means I have to update my template to this:
<template>
<div v-if="result">
<category-title
v-if="result.category.value"
:category="result.category.value"
>
</category-title>
<base-loader :loading="result.loading.value"> </base-loader>
<products
:category="result.category.value"
v-if="result.category.value"
></products>
</div>
</template>
<script src="./category.component.ts" lang="ts"></script>
<style src="./category.component.scss" lang="scss" scoped></style>
Which is just ugly.
My question is this, can I destructure the computed property or something so my template can stay the same as it was?
You can destructure object returned by your computed but you will lose reactivity (category, loading, error variables created by destructuring computed value will not be updated when computed re-evaluates)
What you want is to use Vue Apollo ability to refresh query when it's variables change
import { useQuery, useResult } from "#vue/apollo-composable";
import * as getCategoryBySlug from "#graphql/api/query.category.gql";
export function useGetCategory(params) {
const { result, loading, error } = useQuery(getCategoryBySlug, params);
const category = useResult(result, null, (data) => data.categoryBySlug);
return { category, loading, error };
}
in component...
import { computed, defineComponent } from "#vue/composition-api";
import { useGetCategory } from "#logic/get-category";
import CategoryTitle from "#components/category-title/category-title.component.vue";
import Products from "#components/products/products.component.vue";
export default defineComponent({
name: "Categories",
components: { CategoryTitle, Products },
setup(_, context) {
const params = computed(() =>
return {
slug: context.root.$route.params.slug
}
)
const { category, loading, error } = useGetCategory(params);
},
});

calling SweetAlert2 inside Async method in VUE 3

I tried to pops out sweetalert if failed to retreive data from server
I've imported sweet alert in main.js :
import VueSweetalert2 from 'vue-sweetalert2'
import 'sweetalert2/dist/sweetalert2.min.css'
const app = createApp(App)
app.use(VueSweetalert2)
app.mount('#app')
And inside the Table.vue components i tried to call swal but got an error says (undefined $this.swal) instead :
<script>
import axios from 'axios'
import { onMounted, ref } from 'vue'
export default {
setup() {
let transactions = ref([])
onMounted(() => {
getTransactions()
})
async function getTransactions() {
try {
let { data } = await axios.get('http://127.0.0.1:8000/api/transactions')
transactions.value = data.data
} catch(e) {
this.$swal('Something went wrong.')
}
}
return {
transactions
}
}
}
</script>
Any suggestion how to solve this ?
You can't use this as the component instance inside setup() because the component has not been created yet. There are other ways to get that $swal property.
vue-sweetalert2 exposes SweetAlert via app.config.globalProperties.$swal or as a provide-ed $swal prop.
A simple way to use it in the Composition API is through inject():
import { inject } from 'vue'
export default {
setup() {
const swal = inject('$swal')
async function getTransactions() {
//...
swal('Something went wrong.')
}
}
}
demo 1
However, the vue-sweetalert2 docs recommend using sweetalert2 directly in this case:
When using "Vue3: Composition API" it is better not to use this wrapper. It is more practical to call sweetalert2 directly.
You can use sweetalert2 directly like this:
import { onMounted, inject } from 'vue'
import Swal from 'sweetalert2'
export default {
name: 'App',
setup() {
async function getTransactions() {
//...
Swal.fire('Something went wrong.')
}
onMounted(() => getTransactions())
}
}
demo 2
In main.js file
import VueSweetalert2 from 'vue-sweetalert2';
import 'sweetalert2/dist/sweetalert2.min.css';
const app = createApp(App);
app.use(VueSweetalert2);
window.Swal = app.config.globalProperties.$swal;
app.mount("#app");
Use Swal.fire() inside COMPOSITION API
export default {
setup() {
function yourFunctionName() {
Swal.fire('Hello !')
}
}
}

Vue TypeError: this is undefined

Why this is undefined here? On logout click this is the error shown in the browser console TypeError: this is undefined
<script lang="ts">
import Vue from "vue";
import { getModule } from "vuex-module-decorators";
import Component from "vue-class-component";
import AuthModule from "#/store/auth";
import Store from "#/store";
const authModule = getModule(AuthModule, Store);
#Component({})
export default class App extends Vue {
mounted() {
console.log("App mounted");
}
onLogoutClick() {
authModule.logout().then(function() {
this.$router.push("/login");
});
}
}
</script>
try this.
methods: {
onLogoutClick() {
let self = this
authModule.logout().then(function() {
self.$router.push("/login");
});
}
Using an arrow function to the anonymous function solves this. As arrow functions bind this to the lexical scope's this (in this case onLogoutClick's this).
onLogoutClick() {
authModule.logout().then(() => {
this.$router.push("/login");
});
}