How Do I correctly use vuex store? - vue.js

I'm trying the following:
I have a login form that uses axios to get results from a rest api. When submitting the correct data I get returned all user data, when submitting the wrong data I get a error message.
I'd like to save the userdata to a global store to use within my vue application. And I figured that vuex would work for this szenario.
Within my main.js I defined vuex and a mutation function to get the user data.
import { createStore } from 'vuex'
// other imports
const store = createStore({
state () {
return {
userData: {}
}
},
mutations: {
addUserData (state, data) {
state.userData = data
}
}
})
createApp(App)
.use(router, store)
.mount('#app')
within my Login.vue I'm trying to use the store I created earlier:
However, when trying to use the mutation for the store addUserData I get Uncaught (in promise) ReferenceError: store is not defined in the console. How do I now correctly use the function?
I tried using this.$store.state.addUserData as well and end up with a different error
<template>
<div class="container content">
<h1>Login</h1>
<form #submit.prevent="login">
<div class="mb-3">
<label for="username" class="form-label">Username:</label>
<input type="username" id="username" v-model="username" class="form-control" placeholder="Username">
</div>
<div class="mb-3">
<label for="password" class="form-label">Password:</label>
<input type="password" id="password" v-model="password" class="form-control" placeholder="•••••••">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
</template>
<script>
import axios from 'axios'
export default {
data(){
return {
username: '',
password: '',
}
},
methods: {
async login() {
axios.get('https://localhost:9443/isValidUser',
{
params: {
user: this.username,
pass: this.password,
}
}).then(response => {
console.log(response.data)
if(response.data[0].username) {
console.log("Hello " + response.data[0].username)
store.commit('addUserData', response.data[0])
} else {
console.log("Login failed")
}
})
}
}
}
</script>

My solution to use multiple plugins at "once" is that :
const app = createApp(App)
[store, router].forEach((p) => app.use(p));
app.mount('#app);
if you have a lot of plugins it can be handy, if not you can always do :
createApp(App)
.use(router)
.use(store)
.mount('#app')
for two plugins it's ok
So in the end your store is not used and hence the error

Related

Get newest data after submit data without page reload using vue script setup + axios

Please kindly help me with this, because I'm new in vue.js.
So, I have 2 Vue files :
Greetinglist.vue, It calls data from api using axios.get
Greeting.vue, It posts data using axios.post
After submit data, how to refresh the data without reload the page ?
I'm using <script setup> tag.
(greetinglist.vue)
<div class="p-2" v-for="(message, index) in messages" :key="index">
<div>
<h3>{{ message.name }}<span>{{message.date}}</span></h3>
<p class="text-slate-300 text-lg">{{ message.greeting }}</p>
</div>
<hr/>
</div>
<script setup>
import {ref, onMounted } from 'vue'
import axios from 'axios'
let messages = ref([]);
function getMessages() {
axios.get('http://127.0.0.1:8000/api/messages')
.then((result) => {
messages.value = result.data
}).catch((err) => {
console.log(err.response.data)
})
}
onMounted(() => {
getMessages()
});
</script>
(Greeting.vue)
<form #submit.prevent="store()">
<div class="">
<input type="text" placeholder="" v-model="messages.name">
<textarea name="" id="" cols="28" rows="10" placeholder="" v-model="messages.greeting"></textarea>
</div>
<button>Submit</button>
</form>
<div>
<GreetingList/>
</div>
<script setup>
import { reactive } from 'vue'
import axios from 'axios'
import GreetingList from './GreetingList.vue'
const messages = reactive({
name: '',
greeting: '',
});
function store() {
axios.post('http://127.0.0.1:8000/api/messages', messages)
.then((result) => {
messages.name = ''
messages.greeting = ''
})
.catch((err) => {
})
}
</script>
You can move getMessages function to greeting.vue, and change greetinglist.vue to use props
greeting.vue
<form #submit.prevent="store()">
<div class="">
<input type="text" placeholder="" v-model="messages.name">
<textarea name="" id="" cols="28" rows="10" placeholder="" v-model="messages.greeting"></textarea>
</div>
<button>Submit</button>
</form>
<div>
<GreetingList :messages="messageList" />
</div>
<script setup>
import { reactive } from 'vue'
import axios from 'axios'
import GreetingList from './GreetingList.vue'
const messages = reactive({
name: '',
greeting: '',
});
let messageList = ref([])
function store() {
axios.post('http://127.0.0.1:8000/api/messages', messages)
.then((result) => {
messages.name = ''
messages.greeting = ''
getMessages()
})
.catch((err) => {
})
}
function getMessages() {
axios.get('http://127.0.0.1:8000/api/messages')
.then((result) => {
messageList.value = result.data
}).catch((err) => {
console.log(err.response.data)
})
}
onMounted(() => {
getMessages()
});
</script>
greetinglist.vue
<script setup>
defineProps(['messages'])
</script>
If I understood your requirement correctly, I am assuming you have both the components in a single page and you want to get the details of newly added greeting from Greeting.vue in the GreetingList.vue reactively (without refreshing the route). If Yes, You can achieve that by calling a getMessages() method on successfully promise of axios.post and then pass the results in the GreetingList.vue.
Demo (I just created it using Vue 2, You can change it accordingly as per Vue 3) :
Vue.component('child', {
// declare the props
props: ['msglist'],
// just like data, the prop can be used inside templates
// and is also made available in the vm as this.message
template: `<div>
<div v-for="(message, index) in msglist" :key="index">
<h3>{{ message.name }}</h3>
<p>{{ message.greeting }}</p>
<hr/>
</div>
</div>`
});
var app = new Vue({
el: '#app',
data: {
messages: {
name: null,
greeting: null
},
// For demo, I am just mock data for initial greeting listing.
messageList: [{
name: 'Alpha',
greeting: 'Hi !'
}, {
name: 'Beta',
greeting: 'Hello !'
}]
},
methods: {
store() {
// Post API call will happen here.
// on success, make a call to get list of greetings.
if (this.messages.name && this.messages.greeting) {
this.getMessages();
}
},
getMessages() {
// Get API call will happen here. For demo, I am just using the mock data and pushing the newly submitted greeting in a exisiting messageList.
this.messageList.push({
name: this.messages.name,
greeting: this.messages.greeting
})
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input type="text" placeholder="Name" v-model="messages.name"/>
<textarea cols="28" rows="10" placeholder="Greeting" v-model="messages.greeting"></textarea>
<button #click="store">Submit</button>
<child :msglist="messageList">
</child>
</div>
<template>
<div>
<h1>Post component</h1>
<form #submit="submitData" method="post">
<input type="text" placeholder="Enter Title" name="tilte" v-model="userdata.title" /><br />
<br />
<input type="text" placeholder="Enter Author" name="author" v-model="userdata.author" /><br /><br />
<button type="submit">Submit Data</button>
</form>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "postcomponent",
data() {
return {
userdata: {
title: null,
author: null,
},
};
},
methods: {
submitData(e) {
axios.post(
"http://localhost:3000/posts",
this.userdata
).then((result) => {
console.warn(result)
});
e.preventDefault();
},
},
};
</script>

Correctly testing vee-validate validated form submit with Jest

I am trying to submit a form that uses vee-validate and test if the form calls the underlying store with Jest.
Here is my code:
Form:
<template>
<div class="flex flex-col justify-center h-screen bg-site-100">
<!-- Login body -->
<div class="container">
<div class="mx-auto w-4/12 p-7 bg-white">
<!-- Form -->
<Form id="loginForm" #submit="login" :validation-schema="schema" v-slot="{ errors }">
<div class="mt-4">
<div>
<text-box
:type="'email'"
:id="'email'"
:label="'Your Email'"
v-model="email"
:place-holder="'Email'"
:required="true"
:error="errors.email"
/>
</div>
<div>
<text-box
:type="'password'"
:id="'password'"
:label="'Parool'"
v-model="password"
:place-holder="'Password'"
:required="true"
:error="errors.password"
/>
</div>
<!-- Submit -->
<Button
type="submit"
id="loginButton"
:disabled="Object.keys(errors).length > 0"
class="text-white bg-site-600 w-full hover:bg-site-700 focus:ring-4 focus:ring-site-300 font-medium rounded-md text-sm px-5 py-2.5 mr-2 mb-2 focus:outline-none"
>
Log In
</Button>
</div>
</Form>
</div>
</div>
</div>
</template>
<script lang="ts">
import * as Yup from "yup";
import { Form } from "vee-validate";
import { defineComponent } from "vue";
import Button from "../core/Button.vue";
import TextBox from "../core/TextBox.vue";
import { mapActions, mapStores } from "pinia";
import { useAuthStore } from "../../store/auth";
import LoginDataType from "../../types/login_data";
export default defineComponent({
name: "Login",
components: { TextBox, Form, Button },
computed: { ...mapStores(useAuthStore) },
data() {
return {
email: "",
password: "",
schema: Yup.object().shape({
email: Yup.string().required("Email is required").email("Email is invalid"),
password: Yup.string().required("Password is required"),
}),
};
},
methods: {
async login() {
console.log("Logged in mock");
let data: LoginDataType = {
email: this.email,
password: this.password,
};
await this.authStore.login(data);
},
},
});
</script>
Store:
import { defineStore } from "pinia";
export const useAuthStore = defineStore("auth", {
state: () => ({
}),
getters: {
},
actions: {
async login(data: LoginDataType) {
// do something
},
}
})
Test:
it('logs in correctly when right username and password sent to API', async () => {
const store = useAuthStore();
jest.spyOn(store, 'login');
const wrapper = mount(Login, {
stubs: ['router-link']
});
const email = wrapper.find('input[id="email"]');
await email.setValue('testEmail#gmail.com');
// Check if model is set
expect(wrapper.vm.email).toBe(testEmail);
const password = wrapper.find('input[id="password"');
await password.setValue('testPw');
// Check if model is set
expect(wrapper.vm.password).toBe(testPw);
// Check form exists
const loginForm = wrapper.find('#loginForm');
expect(loginForm.exists()).toBe(true);
await loginForm.trigger('submit');
// Check if store method has been called
expect(store.login).toHaveBeenCalled();
expect(store.login).toHaveBeenCalledWith({
email: 'testEmail#gmail.com',
password: 'testPw'
})
});
The test fails at expect(store.login).toHaveBeenCalled(). Implying the form doesn't get submitted. The test works just fine when I replace the vee-validate component Form with a regular HTML form tag.
What might be causing this behaviour any help is highly appreciated? :)

Unable to Define Variable in Vue

I'm just starting to use VueJS & Tailwind, having never really used anything related to npm before.
I have the below code, making use of Tailwind & Headless UI which through debugging, I know I'm like 99% of the way there... except for the continuous error message
Uncaught ReferenceError: posts is not defined
I know this should be straight forward, but everything I've found either here or with Google hasn't worked. Where am I going wrong?
<template>
<Listbox as="div" v-model="selected">
<ListboxLabel class="">
Country
</ListboxLabel>
<div class="mt-1 relative">
<ListboxButton class="">
<span class="">
<img :src="selected.flag" alt="" class="" />
<span class="">{{ selected.name }}</span>
</span>
<span class="">
<SelectorIcon class="" aria-hidden="true" />
</span>
</ListboxButton>
<transition leave-active-class="" leave-from-class="opacity-100" leave-to-class="opacity-0">
<ListboxOptions class="">
<ListboxOption as="template" v-for="country in posts" :key="country" :value="country" v-slot="{ active, selected }">
<li :class="">
<div class="">
<img :src="country.flag" alt="" class="" />
<span :class="[selected ? 'font-semibold' : 'font-normal', 'ml-3 block truncate']">
{{ country.latin }}
</span>
</div>
<span v-if="selected" :class="">
<CheckIcon class="" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</template>
<script>
import { ref } from 'vue'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '#headlessui/vue'
import { CheckIcon, SelectorIcon } from '#heroicons/vue/solid'
import axios from 'axios'
export default {
data() {
return {
response: null,
posts: undefined,
};
},
components: {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
mounted: function() {
axios.get('http://localhost')
.then(response => {
this.posts = response.data;
});
},
setup() {
const selected = ref(posts[30])
return {
selected,
}
},
}
</script>
The offending line is const selected = ref(posts[30]) which I know I need to somehow define posts, but I don't get how?
CAUSE OF YOUR ERROR:
You are trying to access an array element before the array is populated. Thus the undefined error.
EXPLANATION
You are using a mix of composition api and options api. Stick to one.
I am writing this answer assuming you will pick the composition api.
Follow the comments in the below snippet;
<script>
// IMPORT ONMOUNTED HOOK
import { ref, onMounted } from 'vue'
import { Listbox, ListboxButton, ListboxLabel, ListboxOption, ListboxOptions } from '#headlessui/vue'
import { CheckIcon, SelectorIcon } from '#heroicons/vue/solid'
import axios from 'axios'
export default {
// YOU DO NOT NEED TO DEFINE THE DATA PROPERTY WHEN USING COMPOSITION API
/*data() {
return {
response: null,
posts: undefined,
};
},*/
components: {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
CheckIcon,
SelectorIcon,
},
// YOU DO NOT NEED THESE LIFE CYCLE HOOKS; COMPOSITION API PROVIDES ITS OWN LIFECYCLE HOOKS
/*mounted: function() {
axios.get('http://localhost')
.then(response => {
this.posts = response.data;
});
},*/
setup() {
// YOU ARE TRYING TO ACCESS AN ELEMENT BEFORE THE ARRAY IS POPULATED; THUS THE ERROR
//const selected = ref(posts[30])
const posts = ref(undefined);
const selected = ref(undefined);
onMounted(()=>{
// CALL THE AXIOS METHOD FROM WITHIN THE LIFECYCLE HOOK AND HANDLE THE PROMISE LIKE A BOSS
axios.get('http://localhost')
.then((res) => {
selected.value = res[30];
});
});
return {
selected,
}
},
}
</script>
According to your comment; you should first check if the “selected != null” before using ‘selected’ inside the template. You can use a shorthand version like this
<img :src=“selected?.flag” />

Vue JS: Forms are not getting cleared when router.push() method is executed after the logout

I am using Flask as a backend and Vue JS as front end for my development. Vuex for state store.
In the logout() I am clearing the authentication token from localStorage via store.dispatch('/logout') and then using router.push('/login') to navigate to Login.vue. I find that the form details entered before are not getting cleared. When the logout() is performed it navigates to the Login.vue with flash message stating 'You have been logged out successfully'.
Below is the code snippet for the same:
logout() {
axios.get(`${this.host}:5000/logout`)
.then((res) => {
this.$store.dispatch('logout')
.then(() => {
this.$router.push('/login');
});
this.flashMessage.success({
message: res.data.msg,
time: 5000,
flashMessageStyle: {
backgroundColor: 'linear-gradient(#e66465, #9198e5)',
},
});
})
.catch((error) => {
this.flashMessage.error({
message: error.toString(),
time: 5000,
flashMessageStyle: {
backgroundColor: 'linear-gradient(#e66465, #9198e5)',
},
});
});
}
logout() is written in App.vue. Here router is an instance of vue-router.
To avoid the issue of form data not being cleared I have used router.go() instead of router.push() in the above code. But because of this implementation, the flash message is not getting displayed as the reload (because of router.go()) is overwriting the displaying of flash message which I don't want.
Please let me if it is possible to erase the form data after logout without getting into the trouble of not showing flash message.
Not sure how you have implemented Login.vue, but here is a Login.vue that I have implemented, and it works. One of the takeaways should be that my form model data values are initialized to empty strings.
<template>
<div id="login">
<form class="form-horizontal">
<div class="form-group">
<label for="username" class="col-md-offset-3 col-md-2 align-right">User Name</label>
<div class="col-md-3">
<input type="input" ref="username" class="form-control" id="username" v-model="username">
</div>
</div>
<div class="form-group">
<label for="password" class="col-md-offset-3 col-md-2 align-right">Password</label>
<div class="col-md-3">
<input type="password" class="form-control" id="password" v-model="password" v-on:keyup.enter="login">
</div>
</div>
<div class="form-group">
<div class="col-md-offset-5 col-md-4">
<button type="button" class="btn btn-default"
v-on:click="login">Login</button>
<span class="error-msg" v-if="errorMsg">{{ errorMsg }}</span>
</div>
</div>
</form>
</div>
</template>
<script>
import { loginUrl, axios, processAjaxLoginError } from '../globalvars.js'
export default {
name: 'Login',
data() {
return {
username: '',
password: '',
errorMsg: ''
}
},
methods: {
login() {
axios.post(loginUrl, {
username: this.username,
password: this.password
})
.then(response => {
// Commit the token to the store
this.$store.commit('updateToken', { token: response.data.message });
// Clear error message
this.errorMsg = '';
// Redirect to customer index view
this.$router.push("/customers")
})
.catch(error => {
this.errorMsg = processAjaxLoginError(error);
})
}
},
computed: {
token() {
return this.$store.state.token;
}
},
mounted() {
this.$refs.username.focus();
}
}
</script>

Get URL query parameters in Vue 3 on Component

i am new to Vue JS, i must say i really love this platform. I started using it just 3 days back. I am just trying to get the URL query parameter and i am using vue-router as well. Here is how i have it:
http://localhost:8001/login?id=1
Here is how my controller look like.
<template>
<section class="contact-info-area">
<div class="container">
<div class="row">
<div class="col-lg-12">
<section class="write-massage-area">
<div class="mb-5"></div>
<div class="row justify-content-center">
<div class="col-lg-5">
<div class="write-massage-content">
<div class="write-massage-title text-center">
<h3 class="title">Login {{ $route.query.id }}</h3> <!-- THIS IS WORKING -->
</div>
<div class="write-massage-input">
<form #submit.prevent="onSubmitForm">
<div class="row">
<div class="col-lg-12">
<div class="input-box mt-10">
<input type="text" placeholder="Email" v-model="form['email']">
<ErrorMessage :validationStatus="v$.form.email"></ErrorMessage>
</div>
</div>
<div class="col-lg-12">
<div class="input-box mt-10">
<input type="text" placeholder="Password" v-model="form['password']">
</div>
</div>
<div class="col-lg-12">
<div class="input-box mt-10 text-center">
<button type="submit" class="main-btn main-btn-2">Login</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<div class="mb-5"></div>
</section>
</div>
</div>
</div>
</section>
</template>
<script>
import { ref } from 'vue'
import useVuelidate from '#vuelidate/core'
import { required, minLength, email } from '#vuelidate/validators'
import ErrorMessage from '../components/ErrorMessage'
export default {
name: "login",
components: {
ErrorMessage
},
created(){
},
setup(){
const form = ref({})
const rules = {
form: {
email: {
required,
email
},
password: {
required,
minLength : minLength(5)
}
}
}
const v$ = useVuelidate(rules, { form })
function onSubmitForm(){
console.log(this.$route.query.id) **<!-- THIS IS NOT WORKING -->**
v$.value.$touch()
console.log(form.value)
}
return { form, onSubmitForm, v$}
}
}
</script>
here on the above code. On button submit i am going to a function called onSubmitForm, there i am using console.log(this.$route.query.id) but this is giving a below error :
Uncaught TypeError: Cannot read property 'query' of undefined
at Proxy.onSubmitForm (Login.vue?a55b:84)
Why this is happening? I am seeing that in the vue document as well, they mentioned in the same way. What i am missing in this?
Thank you!
You can call useRoute to access the query params...
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
console.log(route.query)
</script>
If you use parameters and your endpoint looks something like this:
{
path: '/inspect_detail/:id',
name: 'inspect_detail',
component: function() {
return import ( '../views/Inspect_detail.vue')
},
params: true
},
and you are routing like this:
<router-link :to="{ name: 'inspect_detail', params: { id: akey }}">...</router-link>
then you can pickup the values like this:
<script>
import { useRoute } from 'vue-router';
export default {
setup(){
const route = useRoute()
console.log( route.params );
}
}
</script>
Bit late but if you want query params to work on page refresh you have to wait for the router to get ready as its asynchronous. The router wont be ready if its a page refresh. The router will be ready if its navigation from another page in that case the query params will be available from the begining.
<script setup>
import { onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
//just to demonstrate
console.log(route.query)
// If it is a page refresh it will print null.
// The router wont be ready if its a page refresh. In that case the query wont be available.
// The router will be ready if its navigation from another page in that case the query params will be available
onMounted(() => {
getUrlQueryParams()
});
getUrlQueryParams = async () => {
//router is async so we wait for it to be ready
await router.isReady()
//once its ready we can access the query params
console.log(route.query)
});
</script>
I hadn't done frontend in a couple of months and had to refresh on Vue 3 and route query properties, and this question came up first.
Now the memory jog has kicked in, I believe the correct way is to pass the props from the router to the component as shown in the examples here https://router.vuejs.org/guide/essentials/passing-props.html
Essentially, call your route
{
path: "/login",
name: "Login",
props: route => ({ id: route.query.id }),
component: () => import(/* webpackChunkName: "login" */ "../views/Login.vue"),
},
to be able to access the id field.
Alternately, you can sent the whole lot with props: route => ({ query: route.query })
Pick it up as a prop in your view/component
export default {
name: "login",
components: {
ErrorMessage
},
created(){
},
props: {
id: {
type: String,
default: "",
}
}
setup(props){
const form = ref({})
const rules = {
form: {
email: {
required,
email
},
password: {
required,
minLength : minLength(5)
}
}
}
const v$ = useVuelidate(rules, { form })
function onSubmitForm(){
console.log(props.id)
v$.value.$touch()
console.log(form.value)
}
return { form, onSubmitForm, v$}
}
}