Vuex 3: State is undefined - vue.js

versions:
"vue": "2.4.2",
"vue-router": "2.7.0",
"vuex": "3.0.1"
I am attempting to mock a simple login with two fields that will in the end have JWT to allow an authenticated login.
However, the state in Vuex refuses to change and will output that it is "undefined"
Starting from Login.vue:
<template>
<div class="login" id="login">
<b-form-input
id="inputfield"
v-model="username"
type="text"
placeholder="username">
</b-form-input>
<b-form-input
id="inputfield"
type="password"
v-model="password"
placeholder="password">
</b-form-input>
<b-button #click="login()" id = "inputfield" variant="outline-success">
Login
</b-button>
</div>
</template>
<script>
export default {
name: 'login',
data () {
return {
username: '',
password: ''
}
},
methods: {
login () {
this.$store.dispatch('login', {
username: this.username,
password: this.password,
isAuthed: true // this is a temp test to see if it changes
})
}
}
}
</script>
The store is as such:
export default new Vuex.Store({
state: {
username: null,
loggedIn: false
},
mutations: {
authUser (state, userData) {
console.log(userData.isAuthed) // True!
state.username = userData.username
state.loggedIn = userData.isAuthed
console.log(state.loggedIn) // Undefined?
console.log(state.username) // Also undefined?
}
},
actions: {
login ({commit}, authData) {
console.log(authData)
commit('authUser', {
authData
})
}
})
In another words, I can follow the isAuthed and username around the flow and they're always present, everything goes wrong when trying to assign a new value to the state. Am I doing it wrong? This is following a guide, however vuex is version 3 here, did they change the way you mutate the state?

The error is here in your login action.
commit('authUser', {
authData
})
That should just be
commit('authUser', authData)
In the original statement you are creating a new object with an authData property. What you want to do is simply pass the authData object to the mutation.
Here is a demonstration of it working.

Related

this.$auth.loggedIn returning false

I'm using laravel/passport for authentication in the backend and nuxtjs as frontend when I send a login request from nuxtjs, in case login success, I get back as a response a token and user informations and then the user will be redirected to /profile page, however in /profile page when I return this.$auth.loggedIn I'm getting false!
login.vue
<script>
export default {
data() {
return {
auth: false,
email: "",
password:""
}
},
methods: {
async login() {
try {
const data = { email: this.email, password: this.password }
await this.$auth.loginWith('local', { data:data})
.then(() => this.$router.push('/profile'))
} catch (e) {
}
}
}
}
</script>
profile.vue
<template>
<div class="mt-6">loggedInUser: {{ loggedInUser }}</div>
</template>
<script>
export default{
data() {
return {
loggedInUser:this.$auth.loggedIn
}
}
}
nuxt.config.js
auth: {
strategies: {
provider: 'laravel/passport',
local: {
user: {
property: false,
autoFetch: true
},
endpoints: {
login: { url: '/login', method: 'post', propertyName: 'token' },
user: { url: '/user', method: 'get' }
},
clientId: 'cleint_id',
clientSecret: 'client_secret'
}
}
},
modules: [
'#nuxtjs/axios',
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'#nuxtjs/auth',
],
axios: {
baseURL: "http://prostudent.test/api"
},
and how nuxt knows that a user is logged in since logging in happens in the backend?
this is directly after I click on login, I get redirected to profile and the response of login is as expected, message, token and infos, but in /profile page seems like I'm not logged in!
Even if you're not using Vuex with your own modules, nuxt/auth creates some state for you. Hence the the presence of this.$store.state.auth.loggedIn. Btw, did you tried appending $store on your profile.vue file? As shown in the documentation.
Like this
<template>
<div class="mt-6">
loggedInUser: {{ $store.state.auth.loggedIn }}
</div>
</template>
Also, open your vue devtools and check the vuex tab, you'll find some nice state there.
This also answers your other question
and how nuxt knows that a user is logged in since logging in happens in the backend?
Nuxt checks the response from the server and depending of it, sets the state of auth.loggedIn to either true or false.
Those are the 2 steps that you need to do to achieve a successful login (use loginWith + setUser).
const succesfulLogin = await this.$auth.loginWith('local', {
data: {
email: this.email,
password: this.password,
},
})
if (succesfulLogin) {
await this.$auth.setUser({
email: this.email,
password: this.password,
})
}
After those, loggedIn may pass to true.
Of course, the user info can be fetched from the backend too. Depends of your use case.
Use a computed property instead of data property :
<template>
<div class="mt-6">loggedInUser: {{ loggedInUser }}</div>
</template>
<script>
export default{
computed: {
loggedInUser(){
return this.$auth.loggedIn
}
}
}

Vuejs Passing dynamic data from parent to child component

I've got a view and a component. I'm trying to do auth here.
As a user, I input username and password, click login. This emits the information to the parent component, which makes a fetch request to API gateway in AWS. This fetch response has a header X-Session-Id that I'm interested in.
I've got the emit bit working fine.
However, I'm unable to pass the header value back to the component, and I'm unable to set new_password_required to true, which would add a new input field for a new password, as well as replace the login button with a reset password button.
I feel like I need to do this with props, but I'm unable to successfully pass the values from parent to child.
Also, should the reset password bit have its own component?
Here's my code below. This is my first frontend, so I'm not familiar with how I am supposed to share it (e.g. with a playground). Also, I'm trying to stick to vanilla vue for now since I'm learning (I've only get vue-router installed I think)
parent:
<template>
<div id="app" class="small-container">
<login-form #login:user="loginUser($event)" />
</div>
</template>
<script>
import LoginForm from "#/components/LoginForm.vue";
export default {
name: "Login",
components: {
LoginForm
},
data() {
return {
session_id: String,
new_password_required: Boolean
};
},
methods: {
async loginUser(loginEvent) {
try {
const response = await fetch(
process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/login/user",
{
method: "POST",
body: JSON.stringify(loginEvent)
}
);
const data = await response.json();
console.log(data);
if (data.headers["X-Session-Id"] != null) {
this.session_id = data.headers["X-Session-Id"];
this.new_password_required = true;
}
} catch (error) {
console.error(error);
}
},
async resetPassword(resetPasswordEvent) {
try {
const response = await fetch(
process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/reset/user/password",
{
method: "POST",
body: JSON.stringify(resetPasswordEvent)
}
);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
}
};
</script>
Component:
<template>
<div id="login-form">
<h1>Serverless App</h1>
<form>
<label for="email_address">Email Address:</label><br />
<input
v-model="login_details.email_address"
type="text"
id="email_address"
name="email_address"
/><br />
<label for="password">Password:</label><br />
<input
v-model="login_details.password"
type="password"
id="password"
name="password"
/>
<label v-if="new_password_required" for="new_password"
>New Password:</label
><br />
<input
v-if="new_password_required"
v-model="login_details.new_password"
type="password"
id="new_password"
name="new_password"
/>
</form>
<button v-if="!new_password_required" #click="loginUser($event)">
Login
</button>
<button v-if="new_password_required" #click="resetPassword($event)">
Reset Password
</button>
</div>
</template>
<script>
export default {
name: "LoginForm",
props: {
session_id: String,
new_password_required: {
type: Boolean,
default: () => false
}
},
data() {
return {
login_details: {
email_address: "",
password: "",
new_password: ""
}
};
},
methods: {
loginUser() {
console.log("testing loginUser...");
const loginEvent = {
email_address: this.login_details.email_address,
password: this.login_details.password
};
this.$emit("login:user", loginEvent);
},
resetPassword() {
console.log("testing resetPassword...");
const resetPasswordEvent = {
email_address: this.login_details.email_address,
password: this.login_details.password,
new_password: this.login_details.new_password,
session_id: this.login_details.sessionId
};
this.$emit("reset:Password", resetPasswordEvent);
}
}
};
</script>
Your child component looks good, however, you need to pass the props through in the parent component as shown here:
<login-form #login:user="loginUser($event)" :session-id="xidhere"
:new-password-required="newPasswordRequired"/>
As these values are updated in the parent component, the child component should be updated.
As a note, name your props using camel case, and then use kebab-case in your HTML.
So your login-form props should be updated to:
props: {
sessionId: String,
newPasswordRequired: {
type: Boolean,
default: () => false
}
},
Also, as you are emitting the event to parent, there may be no need to send the session id to the child, just add this to your api call before you send it.
Figured it out. I created a new child component for resetting password. Perhaps it can be dry'd up a bit? I'm new at this. Happy for any pointers :)
PARENT
<template>
<div id="app" class="small-container">
<login-form v-if="!new_password_required" #login:user="loginUser($event)" />
<reset-password-form
v-if="new_password_required"
:session_id="session_id"
#reset:password="resetPassword($event)"
/>
</div>
</template>
<script>
import LoginForm from "#/components/LoginForm.vue";
import ResetPasswordForm from "#/components/ResetPasswordForm.vue";
export default {
name: "Login",
components: {
LoginForm,
ResetPasswordForm
},
data() {
return {
session_id: "",
new_password_required: false
};
},
methods: {
async loginUser(loginEvent) {
try {
const response = await fetch(
process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/login/user",
{
method: "POST",
body: JSON.stringify(loginEvent)
}
);
const data = await response.json();
console.log(data);
if (data.headers["X-Session-Id"] != null) {
this.session_id = data.headers["X-Session-Id"];
this.new_password_required = true;
}
} catch (error) {
console.error(error);
}
},
async resetPassword(resetPasswordEvent) {
try {
const response = await fetch(
process.env.VUE_APP_API_GATEWAY_ENDPOINT + "/reset/user/password",
{
method: "POST",
body: JSON.stringify(resetPasswordEvent)
}
);
const data = await response.json();
console.log(data);
} catch (error) {
console.error(error);
}
}
}
};
</script>
CHILD: login-form
<template>
<div id="login-form">
<h1>Serverless Release Dashboard</h1>
<form>
<label for="email_address">Email Address:</label><br />
<input
v-model="login_details.email_address"
type="text"
id="email_address"
name="email_address"
/><br />
<label for="password">Password:</label><br />
<input
v-model="login_details.password"
type="password"
id="password"
name="password"
/>
</form>
<button #click="loginUser($event)">
Login
</button>
</div>
</template>
<script>
export default {
name: "LoginForm",
data() {
return {
login_details: {
email_address: "",
password: ""
}
};
},
methods: {
loginUser() {
console.log("testing loginUser...");
const loginEvent = {
email_address: this.login_details.email_address,
password: this.login_details.password
};
this.$emit("login:user", loginEvent);
}
}
};
</script>
CHILD: reset-password-form
<template>
<div id="reset-password-form">
<h1>Serverless Release Dashboard</h1>
<form>
<label for="email_address">Email Address:</label><br />
<input
v-model="login_details.email_address"
type="text"
id="email_address"
name="email_address"
/><br />
<label for="password">Password:</label><br />
<input
v-model="login_details.password"
type="password"
id="password"
name="password"
/>
<label for="new_password">New Password:</label><br />
<input
v-model="login_details.new_password"
type="password"
id="new_password"
name="new_password"
/>
</form>
<button #click="resetPassword($event)">
Reset Password
</button>
</div>
</template>
<script>
export default {
name: "ResetPasswordForm",
props: {
email_address: String,
password: String,
session_id: String
},
data() {
return {
login_details: {
email_address: "",
password: "",
new_password: "",
session_id: ""
}
};
},
methods: {
resetPassword() {
console.log("testing resetPassword...");
const loginEvent = {
email_address: this.email_address,
password: this.password,
new_password: this.login_details.new_password,
session_id: this.session_id
};
this.$emit("reset:password", loginEvent);
}
}
};
</script>

Get UserName and Make UserName reactive after Vue Authentication

I am having trouble getting user data and making user data reactive after the user has logged In.
Without using Store i am getting the user information but I am unable to make it reactive. So I tried storing user information in store. Now I am having getting that data as well.
I have a login form in LOGINCOMPONENT.VUE that has two input fields email and password.
<form #submit.prevent="login">
<input placeholder="Email" type="email" v-model="formData.email">
<input placeholder="Password" type="password" v-model="formData.password">
</form>
Script portion:
export default {
name: 'LoginPage',
data() {
return {
formData: {},
};
},
methods: {
login() {
this.$axios.post('login', this.formData).then(async (res) => {
await localStorage.setItem('user', JSON.stringify(res));
await localStorage.setItem('token', res.token);
this.$router.push('/');
console.log(res);
this.$store.dispatch('userDataAction', res); --->>> Using Store to take user data
}).catch((error) => {
console.log(error);
});
},
},
};
Login process goes well and user token is generated.
This is my store.
const state = {
token: localStorage.getItem('token') || null,
userData: {},
};
const getters = {
getUserData: state => state.userData,
loggedIn: state => state.token != null,
};
const mutations = {
userDataMutation(state, userData) {
state.userData = userData;
},
};
const actions = {
userDataAction(context, credentials) {
const userData = {
username: credentials.username,
email: credentials.email,
firstName: credentials.first_name,
lastName: credentials.last_name,
};
context.commit('userDataMutation', userData);
},
};
Finally in my HEADERCOMPONENT.VUE where i am showing "SIGN IN" if user is not logged In and "HELLO USERNAME" if user is logged in.
export default {
name: 'HeaderComponent',
computed: {
...mapGetters(['getUserData', 'loggedIn']),
},
};
Template:
<div> {{ loggedIn ? getUserData.username : 'Sign In' }} </div>

Triggered event on b-button doesn't show on wrapper.emitted()

I'm new to testing vue components and right now I'm trying to test that clicking a b-button component trigger an event and calls a method.
The test seems to fail because the 'click' event doesn't get triggered.
Also note that i'm using Nuxt.js
The component containing b-button is the following:
<template>
<div>
<b-form-input name="username" type="text" v-model="username"></b-form-input>
<b-form-input name="password" type="password" v-model="password"></b-form-input>
<b-button #click="login">Login</b-button>
</div>
</template>
<script>
import { mapActions } from "vuex";
export default {
data() {
return {
username: "",
password: ""
};
},
methods: {
...mapActions({
dispatchLogin: "auth/login"
}),
login: () => {
const response = this.dispatchLogin(this.username, this.password);
response.then(this.$router.push("/dashboard"));
}
}
};
</script>
And the test is written like that:
test('call login when Login button gets clicked', async () => {
expect.assertions(2);
moxios.stubRequest('/api/auth/login', {
response: 200
});
const store = new Vuex.Store(storeConfig);
const mocks = {
login: jest.fn()
}
const wrapper = shallowMount(login, { localVue, store, mocks });
debugger;
// Note that b-button is stubbed as <button></button>
wrapper.find('button').trigger('click');
await flushPromises()
expect(wrapper.emitted('click')).toHaveLength(1);
expect(auth.actions.login).toHaveBeenCalledTimes(1);
});
The problem is that I can't find any click event in wrapper.emitted().

Vuex: [Vue warn]: Computed property "username" was assigned to but it has no setter

I am attempting to set and get a username, password, and an authentication boolean from the global vuex state and conditionally render some elements in a navbar. Here is the login component which should pass the data:
<template>
<div class="login" id="login">
<b-form-input
id="inputfield"
v-model="username"
type="text"
placeholder="username">
</b-form-input>
<b-form-input
id="inputfield"
type="password"
v-model="password"
placeholder="password">
</b-form-input>
<b-button #click="login()" id = "inputfield" variant="outline-success">
Login
</b-button>
</div>
</template>
<script>
export default {
name: 'login',
computed: {
username () {
return this.$store.state.username
},
password () {
return this.$store.state.password
},
loggedIn () {
return this.$store.state.loggedIn
}
},
methods: {
login () {
this.$store.dispatch('login', {
username: this.username,
password: this.password,
isAuthed: true // just to test
})
}
}
}
</script>
However, when i enter anything in the input fields, Vue throws a warning for that field(and the state will not update):
[Vue warn]: Computed property "username" was assigned to but it has no setter.
You are using v-model with a computed property, so you are actually trying to update that computed property when an input event fires.
Therefore, you need to have setters for your computed properties.
As you are trying to use Vuex state, your setters for your computed properties can commit mutations to the store.
computed: {
username : {
get () {
return this.$store.state.username
},
set (value) {
this.$store.commit('updateUsername', value)
}
},
password : {
get () {
return this.$store.state.password
},
set (value) {
this.$store.commit('updatePassword', value)
}
},
...
},
You will need to have the corresponding mutation for your store, for example:
mutations: {
updateUsername (state, username) {
state.username = username
},
updatePassword (state, password) {
state.username = password
},
...
}
For more detail, see the explanation in the Vuex docs here:
https://vuex.vuejs.org/en/forms.html
For your login button, you are trying to dispatch an action. If you just want to set a boolean value in the store then you can dispatch the action like this:
methods: {
login () {
this.$store.dispatch('login', true)
}
}
...and then in your store you will need a corresponding action and mutation to commit:
mutations: {
login (state, value) {
state.isAuthed = value
}
},
actions: {
login ({ commit }) {
commit('login')
}
}
Here are the docs for actions:
https://vuex.vuejs.org/en/actions.html
I hope this helps.