Since converting from Vuex classic to module mode, my login function is broken
index.vue login interface
login() {
if (this.$refs.loginForm.validate()) {
const email = this.loginData.email
const password = this.loginData.password
this.$notifier.showMessage({ text: 'Logging in', color: 'primary' })
this.$store.dispatch('user/userLogin', { email, password }).then(
(result) => {
//
},
(error) => {
this.$notifier.showMessage({ text: error, color: 'error' })
}
)
}
store/user.js mutations
const state = () => ({
user: null,
isAuthenticated: false
})
const mutations = {
setUser(state, payload) {
state.user = payload
},
setIsAuthenticated(state, payload) {
state.isAuthenticated = payload
}
}
store/user.js login action
userLogin({ commit }, { email, password }) {
return new Promise((resolve, reject) => {
auth
.signInWithEmailAndPassword(email, password)
.then((user) => {
console.log('logged in')
commit('setUser', user)
commit('setIsAuthenticated', true)
this.$router.push({
name: 'home'
})
resolve(true)
})
.catch((error) => {
commit('setUser', null)
commit('setIsAuthenticated', false)
this.$router.push({
path: '/'
})
reject(error)
})
})
},
When the login is clicked, the console is flooded with errors like
client.js?06a0:77 Error: [vuex] do not mutate vuex store state outside mutation handlers.
at assert (vuex.esm.js?2f62:90)
at Vue.store._vm.$watch.deep (vuex.esm.js?2f62:789)
at Watcher.run (vue.runtime.esm.js?2b0e:4568)
at Watcher.update (vue.runtime.esm.js?2b0e:4542)
at Dep.notify (vue.runtime.esm.js?2b0e:730)
at B.reactiveSetter [as a] (vue.runtime.esm.js?2b0e:1055)
at Tb (auth.esm.js?b7aa:27)
at B.k.Oc (auth.esm.js?b7aa:26)
at lc (auth.esm.js?b7aa:29)
at hc (auth.esm.js?b7aa:29)
It seems to have to do with the commit('setUser', user). As i am using v-model for the inputs (email,password) I tried slice() / making a copy of the input values to no effect. What am i missing here?
EDIT: Added template
<template>
...
<v-card-text>
<v-form
ref="loginForm"
v-model="valid"
lazy-validation
#submit.prevent="login()"
>
<v-text-field
v-model="loginData.email"
label="Email"
autofocus
clearable
:rules="[rules.required, rules.length]"
prepend-icon="mdi-account-circle"
#blur="resetValidation()"
/>
<v-text-field
v-model="loginData.password"
:type="showPassword ? 'text' : 'password'"
label="Password"
:rules="[rules.required]"
prepend-icon="mdi-lock"
:append-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'"
#click:append="showPassword = !showPassword"
#blur="resetValidation()"
/>
...
Related
I'm currently creating an authentication feature in Nuxt and Firebase.
The login and logout process itself can be done and the header display changes accordingly, but there is an error in console when I press the login button.
Error content (in console)
Uncaught RangeError: Maximum call stack size exceeded
at Function.keys (<anonymous>)
code
Header.vue(This is the page containing the login button.)↓
googleLogin () {
const provider = new firebase.auth.GoogleAuthProvider()
auth.signInWithPopup(provider)
.then(res => {
this.dialogAuthVisible = false
this.$store.dispatch('auth/setUser',res.user)
}).catch(e => console.log(e))
}
store/auth.js↓
export const strict = false
export const state = () => ({
user: null
})
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
export const getters = {
isAuthenticated (state) {
return !!state.user
}
}
default.vue↓
mounted () {
auth.onAuthStateChanged(user => {
const { uid, displayName, photoURL} = user
if (user) {
this.$store.dispatch('auth/setUser', { uid, displayName, photoURL})
} else {
this.$store.dispatch('auth/setUser', null)
}
})
}
If there's any information I'm missing, please let me know 🙇️.
Please teach me how to do this 🙇️.
I think the problem is in this code lines :
export const mutations = {
SET_USER (state, payload) {
state.user = payload
}
}
export const actions = {
setUser ({ commit }, user) {
commit('SET_USER',user)
}
}
There is a loop between this mutations and actions
Instead of setting the entire payload into the store object, I just picked the fields I needed, and that resolved the problem for me.
Before:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
commit('SetAuthUser', data.authUser);
} else {
commit('SetAuthUser', null);
}
}
After:
AUTH_STATUS_CHANGED ({commit}, data: any): any {
if (data && data.authUser) {
const user = data.authUser;
commit('SetAuthUser', {
uid: user.uid,
email: user.email,
emailVerified: user.emailVerified,
displayName: user.displayName,
isAnonymous: user.isAnonymous,
photoURL: user.photoURL,
stsTokenManager: user.stsTokenManager,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt,
apiKey: user.apiKey,
});
} else {
commit('SetAuthUser', null);
}
}
Inside the mutation, just add the value received from the mutation payload.
Data does not get added to database after adding email and password in authorization in Firebase through vue.js.
After:
firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
This doesn't get executed:
ref.set({
alias: this.alias,
geolocation: null,
user_id: cred.user.uid
})
Email and Password gets added to Authorization. But other fields like alias, geolocation, user_id does not get added to the database.
import slugify from 'slugify'
import db from '#/firebase/init'
import firebase from 'firebase'
export default {
name: 'Signup',
data(){
return {
email: null,
password: null,
alias: null,
feedback: null,
slug: null,
}
},
methods: {
signup() {
if(this.alias && this.email && this.password) {
this.feedback = null
this.slug = slugify(this.alias, {
replacement: '-',
remove: /[$*_+~.()'"!\-:#]/g,
lower: true
})
let ref = db.collection('users').doc(this.slug)
ref.get().then(doc => {
if(doc.exists){
this.feedback = 'This alias already exists'
}
else {
firebase.auth().createUserWithEmailAndPassword(this.email, this.password)
.then(cred => {
ref.set({
alias: this.alias,
geolocation: null,
user_id: cred.user.uid
})
}).then(() => {
this.$router.push({ name: 'GMap' })
})
.catch(err => {
console.log(err)
this.feedback = err.message
})
}
})
} else {
this.feedback = 'You must enter all fields'
}
}
}
}
One solution could be to use cloud functions to write to the database on userCreate
https://firebase.google.com/docs/functions/auth-events
Using the chirp-js-sdk wasn't a problem with Next.js but since my app had a requirement to receive data sent by chirp on the browser, I had to use the WebAssembly SDK. Where and how do I instantiate and use the chirpsdk?
I have had no problems in setting it up as explained here.
However, the onReceived, onReceiving callbacks don't get called.
This is a snippet of the page that I am using the chirpsdk in.
class AttendEvent extends Component {
static async getInitialProps({req, query, res}) {
return {
attendString: query
}
}
constructor() {
super();
this.sdk = null;
this.state = {
user: '',
started: false,
waiting: false,
receiving: false,
received: 'Awaiting messages..',
disabled: false
}
}
toAscii(payload) {
let str = ''
for (let i = 0; i < payload.length; i++) {
str += String.fromCharCode(payload[i])
}
return str
}
async startSDK() {
this.sdk = await import ('../node_modules/chirpsdk/index').then( ({Chirp}) => {
return Chirp({
key: CHIRP_API_KEY,
onReceiving: () => {
console.log('Receving Data');
this.setState({
...this.state,
received: '...',
disabled: true
})
},
onReceived: data => {
console.log("Received Data");
if (data.length > 0) {
this.setState({
...this.state,
received: this.toAscii(data),
disabled: false
})
} else {
this.setState({
...this.state,
received: `I didn't hear that. Try turning the volume up?`,
disabled: false
})
}
}
})
})
this.setState({
...this.state,
started: true
})
}
componentDidMount() {
loadFirebase().auth().onAuthStateChanged(user => {
if (user) {
this.setState({
...this.state,
user: user
})
return user
.getIdToken()
.then(token => {
return fetch('/api/login', {
method: 'POST',
headers: new Headers({ 'Content-Type': 'application/json' }),
credentials: 'same-origin',
body: JSON.stringify({ token })
})
})
} else {
Router.push('/');
}
})
this.setState({
...this.state,
waiting: true
})
}
handleLogout() {
loadFirebase().auth().signOut()
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<Navbar page="AttendEvent" />
{
this.state.user !== null ? (
<Grid
container
spacing={0}
direction="row"
alignItems="center"
justify="center"
style={{ minHeight: '90vh' }}
>
<Typography variant="h1">
{
this.state.waiting ? "Waiting for code..." : (this.state.receiving ? "Receiving code...": (this.state.received ? this.state.received : "Mounting...") )
}
</Typography> <br/>
<Button variant="contained" color="primary" onClick={() => {
this.startSDK()
}}>
START
</Button>
</Grid>) : true
}
</React.Fragment>);
}
}
I expected the onReceived callback to be called on receiving 16kHz-mono audio messages but it was not called.
Found a solution to my own question and this seems to work well.
The problem lies with the dynamic import and the way the promise was handled.
async startSDK() {
this.sdk = await import ('../node_modules/chirpsdk/index')
.then(({Chirp}) => {
return Chirp ({
// rest of the initialisation parameters
})
)
.catch( error => {
console.log(error.message);
}
this.setState({
...this.state,
started: true
})
}
The way it should have been done. Although, this could have been done with then..catch as well.
async startSDK(){
try {
const chirp = await import('../node_modules/chirpsdk/index');
const { Chirp } = chirp;
this.sdk = await Chirp ({
// rest of the initialisation parameters
)}
}
catch (err) {
console.log(err);
}
this.setState({
...this.state,
started: true
})
}
A SIDENOTE ON WHY A DYNAMIC IMPORT WAS USED
Initially, I thought Next.js would handle the import of chirpsdk just as React did. However, I suppose because of the SSR nature of Next, the import doesn't work as expected with this being the error being thrown:
Assertion failed: undefined
Assertion failed: undefined
abort("Assertion failed: undefined") at Error
at /Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:10515
at z (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:10647)
at sn (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:75732)
at O (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:5568)
at Ne (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:14094)
at /Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:22110
at Object.openDatabase (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:21497)
at Object.staticInit (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:21983)
at /Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:27857
at E.locateFile (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:131)
at Object.<anonymous> (/Users/warrenwhite/Documents/MajorProj/faculty-dashboard-app-ssr/node_modules/chirpsdk/index.js:1:226)
at Module._compile (module.js:653:30)
at Object.Module._extensions..js (module.js:664:10)
at Module.load (module.js:566:32)
at tryModuleLoad (module.js:506:12)
at Function.Module._load (module.js:498:3)
I'm actually building a react-native & firebase group chat and I need to separate users in there proper channels. I have 6 channels with there proper chat and I don't want the fact that a user can create his channel. In my app, I want to authorize the access to 5 users in Group A, 8 users in B group, etc. I currently have a firebase database that look like this
and my rules are :
{
"rules": {
"GeneralMessage": {
".read": "root.child('user').child(auth.uid).child('inGeneral').child('GeneralMessage').child('read').val() === true",
".write": "root.child('user').child(auth.uid).child('inGeneral').child('GeneralMessage').child('write').val() === true"
}
}
}
But this don't let me access to read and write options.
Edit: The react native code where I push the chat in room.
import * as types from './actionTypes'
import firebaseService from '../../services/firebase'
const FIREBASE_REF_MESSAGES = firebaseService.database().ref('/GeneralMessage')
const FIREBASE_REF_MESSAGES_LIMIT = 20
export const sendMessage = message => {
return (dispatch) => {
dispatch(chatMessageLoading())
let currentUser = firebaseService.auth().currentUser
let createdAt = new Date().getTime()
let chatMessage = {
text: message,
createdAt: createdAt,
user: {
id: currentUser.uid,
email: currentUser.email,
}
}
FIREBASE_REF_MESSAGES.push().set(chatMessage, (error) => {
if (error) {
dispatch(chatMessageError(error.message))
} else {
dispatch(chatMessageSuccess())
}
})
}
}
export const updateMessage = text => {
return (dispatch) => {
dispatch(chatUpdateMessage(text))
}
}
export const loadMessages = () => {
return (dispatch) => {
FIREBASE_REF_MESSAGES.limitToLast(FIREBASE_REF_MESSAGES_LIMIT).on('value', (snapshot) => {
dispatch(loadMessagesSuccess(snapshot.val()))
}, (errorObject) => {
dispatch(loadMessagesError(errorObject.message))
})
}
}
const chatMessageLoading = () => ({
type: types.CHAT_MESSAGE_LOADING
})
const chatMessageSuccess = () => ({
type: types.CHAT_MESSAGE_SUCCESS
})
const chatMessageError = error => ({
type: types.CHAT_MESSAGE_ERROR,
error
})
const chatUpdateMessage = text => ({
type: types.CHAT_MESSAGE_UPDATE,
text
})
const loadMessagesSuccess = messages => ({
type: types.CHAT_LOAD_MESSAGES_SUCCESS,
messages
})
const loadMessagesError = error => ({
type: types.CHAT_LOAD_MESSAGES_ERROR,
error
})
Your datastructure and security rules are not completely matching. You have a rule for /user/user.uid/inGeneral/GeneralMessage/read but there isn't a GeneralMessage child under inGeneral in your datastructure.
With your current datastrcuture your rules have to look like this:
{
"rules": {
"GeneralMessage": {
".read": "root.child('user').child(auth.uid).child('inGeneral').child('general').val() === true",
".write": "root.child('user').child(auth.uid).child('inGeneral').child('general').val() === true"
}
}
}
I've got firebase auth setup, however, I'm trying to update username before setting that to my current state. When I run everything before the update, everything works fine, but I dont have a username. I'm fairly new with js promises so I've tried running the function and returning the function, neither have really worked. My expectation is that by the time the dashboard screen shows, that the username is set.
Below is the code for signup via email which works without username.
store/user/index.js
signUserUp ({commit}, payload) {
commit('setLoading', true)
commit('clearError')
firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password)
.then(function (user) {
return user.updateProfile({
displayName: payload.username
})
})
.then(
profile => {
commit('setLoading', false)
const newUser = {
id: profile.uid,
name: profile.username,
email: profile.email,
photoUrl: profile.photoURL
}
commit('setUser', newUser)
}
)
.catch(
error => {
commit('setLoading', false)
commit('setError', error)
console.log(error)
}
)
}
This is the code that returns an error and does not update the username until I refresh.
signUserUp ({commit}, payload) {
commit('setLoading', true)
commit('clearError')
firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password)
.then(function (user) {
return user.updateProfile({
displayName: payload.username
})
.then(
profile => {
commit('setLoading', false)
const newUser = {
id: profile.uid,
name: payload.username,
email: profile.email,
photoUrl: profile.photoURL
}
commit('setUser', newUser)
}
)
.catch(
error => {
commit('setLoading', false)
commit('setError', error)
console.log(error)
}
)
},
My view is really simple just displaying the data.
<template>
<div>
<h1>Dashboard</h1>
<button #click="onLogout">Logout</button>
<hr>
<app-alert v-if="error" #dismissed="onDismissed" :text="error.message"></app-alert>
<img :if="user.photoURL" :src="user.photoUrl">
<h4><b>Display Name :</b> {{ user.name }}</h4>
<h4><b>Email :</b> {{ user.email }}</h4>
<h4><b>Email Verified :</b> {{ user.emailVerified }}</h4>
<h4><b>User ID :</b> {{ user.id }}</h4>
</div>
</template>
<script>
export default {
date () {
return {}
},
computed: {
user () {
return this.$store.getters.user
},
error () {
return this.$store.getters.error
}
},
methods: {
onLogout () {
this.$store.dispatch('logout')
this.$router.push('/')
},
onDismissed () {
this.$store.dispatch('clearError')
}
}
}
</script>
The errors I get back are an alert that states
Cannot read property 'uid' of undefined
And also, username does not display on the page component, even though it does display on page refresh.
Everything works fine up until I add this bit to try and update the username on user create, so this little bit doesn't pass the new updated user object.
.then(function (user) {
return user.updateProfile({
displayName: payload.username
})
})
Looks like my problem was in how I was sequencing my functions, in place of returning the user.updateProfile function, I was able to nest my updateProfile call like below, additionally, I was calling username as the object username when it should've been displayName.
signUserUp ({commit}, payload) {
commit('setLoading', true)
commit('clearError')
firebase.auth().createUserWithEmailAndPassword(payload.email, payload.password)
.then(function (user) {
user.updateProfile({
displayName: payload.username
}).then(
() => {
commit('setLoading', false)
const newUser = {
id: user.uid,
name: user.displayName,
email: user.email,
emailVerified: user.emailVerified
}
commit('setUser', newUser)
}
).catch(
error => {
commit('setLoading', false)
commit('setError', error)
console.log(error)
}
)
})
.catch(
error => {
commit('setLoading', false)
commit('setError', error)
console.log(error)
}
)
},