Firebase signInWithEmailAndPassword not working in vue.js - firebase

I'm trying to get the sign in part working on my webapp but it's not working properly.
Whenever I press the login button the page either refreshes and the url gets updated with the credentials and stays at the same page OR the router gets pushed and goes to the 'homepage' without logging the user in.
I also followed this guide for reference: https://blog.logrocket.com/vue-firebase-authentication/
What's weird is that the sign up part is working just fine.
SignIn.vue
<div class="card-body">
<form>
<!-- email -->
<div class="input-group form-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-user"></i></span>
</div>
<input id="email" type="email" class="form-control" name="email" placeholder="e-mail" value required autofocus v-model="form.email" />
</div>
<!-- password -->
<div class="input-group form-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fas fa-key"></i></span>
</div>
<input id="password" type="password" class="form-control" name="password" placeholder="password" required v-model="form.password" />
</div>
<!-- error -->
<div v-if="error" class="alert alert-danger animated shake">{{ error }}</div>
<br />
<!-- login -->
<div class="form-group d-flex justify-content-between">
<div class="row align-items-center remember"><input type="checkbox" v-model="form.rememberMe" />Remember Me</div>
<input type="submit" #click="submit" value="Login" class="btn float-right login_btn" />
</div>
</form>
</div>
Script in SignIn.vue
<script>
import firebase from 'firebase';
export default {
data() {
return {
form: {
email: '',
password: '',
rememberMe: false
},
error: null
};
},
methods: {
submit() {
firebase
.auth()
.signInWithEmailAndPassword(this.form.email, this.form.password)
.catch(err => {
this.error = err.message;
})
.then(data => {
this.$router.push({ name: 'home' });
});
}
}
};
</script>
Store.js
import Vue from 'vue';
import Vuex from 'vuex';
import profile from './modules/profile';
import authenticate from './modules/authenticate';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
profile,
authenticate
}
});
Authenticate.js in store
const state = {
user: {
loggedIn: false,
data: null
}
};
const getters = {
user(state) {
return state.user;
}
};
const mutations = {
SET_LOGGED_IN(state, value) {
state.user.loggedIn = value;
},
SET_USER(state, data) {
state.user.data = data;
}
};
const actions = {
fetchUser({ commit }, user) {
commit('SET_LOGGED_IN', user !== null);
if (user) {
commit('SET_USER', {
displayName: user.displayName,
email: user.email
});
} else {
commit('SET_USER', null);
}
}
};
export default {
state,
mutations,
actions,
getters
};

It is probably because you assign the submit type to your button, your form is submitted before the Firebase method is triggered.
You should change the button code from
<input type="submit" #click="submit" value="Login" class="btn float-right login_btn" />
to
<input type="button" #click="submit" value="Login" class="btn float-right login_btn" />
See the W3 specification for more detail on button types.

Related

Passing a function as props in Next.js

I am attempting to pass the function submitForm from formControl to index.js (Home). My goal: to remove form, upon successful form submission and show a success message. I have looked at several examples, but I am still missing something.
I am using Next.js, but I don't think the Next.js usage would be any different from react.js in this case. Also using react-hook-form, once again, I don't think RHF is interrupting the process.
index.js Home view
import { useState } from 'react';
import { useForm } from "react-hook-form";
import styles from '../styles/Home.module.css'
import { proteinArr, starchArr, greensArr} from '../utils'
import date from 'date-and-time';
export default function Home({ submitForm }) {
const { register, handleSubmit, control, formState: { errors } } = useForm();
const onSubmit = (data, e) => {
e.target.reset()
submitForm()
}
const now = new Date();
let day = date.format(now, 'dddd').toLowerCase();
// console.log(SaturdayArr)
// console.log(date.format(now, 'dddd').toLowerCase())
return (
<>
<form onSubmit={handleSubmit((onSubmit))}>
<div className={`${styles.home_card} card home_card `}>
<div className="card-body">
<h2 className={styles.title}>Pick your plate!</h2>
<div className={`${styles.home_selectGroup} input-group mb-3`}>
<div className="input-group-prepend">
<span className="input-group-text" >Name</span>
</div>
<input type="text" className="form-control" {...register("name", { required: true })} placeholder="Order Name" aria-label="Order Name" aria-describedby="basic-addon" />
{errors.name && <p>Order name is required</p>}
</div>
<div className={`${styles.home_selectGroup} input-group mb-3`}>
<div className="input-group-prepend">
<span className="input-group-text" >Email</span>
</div>
<input type="email" id="email" className="form-control" {...register("email")} placeholder="Email#provider.com" aria-label="Email" aria-describedby="basic-addon" />
</div>
<div className={`${styles.home_selectGroup} input-group mb-3`}>
<div className="input-group-prepend">
<span className="input-group-text" >Phone Number</span>
</div>
<input type="tel" id="phone" className="form-control" {...register("phone", { required: true })} placeholder="111-111-1111" aria-label="Phone Number" aria-describedby="basic-addon" />
{errors.name && <p>Phone number is required</p>}
</div>
<div className={`${styles.home_selectGroup} input-group mb-3`}>
<div className="input-group-prepend">
<label className={`${styles.homeLabel} input-group-text`} htmlFor="inputGroupSelect01">Protein</label>
</div>
<select {...register("protein", { required: true })}
className={`${styles.home_select} custom-select" id="inputGroupSelect01`}
>
<option className={styles.home_option}>Choose...</option>
{proteinArr && proteinArr.map((item) => <option key={item}>{item}</option>)}
</select>
{errors.protein && <p>Protein is required</p>}
</div>
<div className={`${styles.home_selectGroup} input-group mb-3`}>
<div className="input-group-prepend">
<label className={`${styles.homeLabel} input-group-text`} htmlFor="inputGroupSelect01">Greens</label>
</div>
<select
{...register("greens", { required: 'select an option' })}
className={`${styles.home_select} custom-select" id="inputGroupSelect01`}>
<option className={styles.home_option}>Choose...</option>
{greensArr && greensArr.map((item) => <option key={item}>{item}</option>)}
</select>
{errors.greens && <p>Green is required</p>}
</div>
<div className={`${styles.home_selectGroup} input-group mb-3`}>
<div className="input-group-prepend">
<label className={`${styles.homeLabel} input-group-text`} htmlFor="inputGroupSelect01">Starch</label>
</div>
<select {...register("starch", { required: true })} className={`${styles.home_select} custom-select" id="inputGroupSelect01`}>
<option className={styles.home_option}>Choose...</option>
{starchArr && starchArr.map((item) => <option key={item}>{item}</option>)}
</select>
{errors.starch && <p>Starch is required</p>}
</div>
<button className="btn btn-primary contact-form_button " type="submit">Submit My Order</button>
</div>
</div>
</form>
</>
)
}
FormControl
import Home from '../../pages/index'
import { useState } from 'react';
const FormControl = () => {
const [isSubmitted, setIsSubmitted] = useState(false);
function submitForm(){
setIsSubmitted(true);
}
return (
<div>
{isSubmitted == false ? <Home submitForm={submitForm}/> : <h2>Your from was submitted</h2>}
</div>
);
}
export default FormControl;
Next.js is just logic on top of React so the way you interact with it is handled the same for both libraries.
However, pages in React and Next.js are both components but pages in Next.js have a lot of functionality added/injected, so you must treat them differently.
Because of this, you cannot just pass props to pages in Next.js like you are as they will always be undefined. You can set page props via getInitialProps, getStaticProps, getServerSideProps, in _document, or even in _app.
With that said, you can simplify your form status by using react-hook-form's successful form submission status.
export default function Home() {
const {formState: { isSubmitSuccessful } } = useForm();
if(isSubmitSuccessful) return <h2>Your from was submitted</h2>;
<form onSubmit={handleSubmit((onSubmit))}>
...form stuff
</form>
}
You could make the form it's own component too
Home page
import HomeForm form './HomeForm'
export default function Home() {
handleSubmit= () => {...submit logic}
return <HomeForm onSubmit={handleSubmit} />
}
Home form component
export default function HomeForm({ onSubmit }) {
const {formState: { isSubmitSuccessful } } = useForm();
if(isSubmitSuccessful) return <h2>Your from was submitted</h2>;
<form onSubmit={handleSubmit((onSubmit))}>
...form stuff
</form>
}

Can't validate email with Vuelidate in Vue3

I'm trying to use validation option in a form. The app is developing under Vue3.
I have installed npm install #vuelidate/core #vuelidate/validator into project folder. In a file main.js I have been trying to add Vuelidate as following:
import { createApp } from 'vue'
import Vuelidate from 'vuelidate'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
import store from './store'
import 'materialize-css/dist/js/materialize.min'
createApp(App).use(store).use(router).use(Vuelidate).mount('#app')
Next I am working on Login.vue file as following
<template>
<form class="card auth-card" #submit.prevent="onSubmit">
<div class="card-content">
<span class="card-title">Example</span>
<div class="input-field">
<input
id="email"
type="text"
v-model.trim="email"
:class="{invalid: ($v.email.$dirty && !$v.email.required) || ($v.email.$dirty && !$v.email.email)}"
>
<label for="email">Email</label>
<small class="helper-text invalid"
v-if="$v.email.$dirty && !$v.email.required"
>Could not be empty</small>
<small class="helper-text invalid"
v-else-if="$v.email.$dirty && !$v.email.email"
>Incorrect form</small>
</div>
</div>
<div class="card-action">
<div>
<button
class="btn waves-effect waves-light auth-submit"
type="submit">
Enter-login
<i class="material-icons right">send</i>
</button>
</div>
</div>
</form>
</template>
<script>
import { email, required, minLength } from '#vuelidate/validators'
import useVuelidate from '#vuelidate/core'
export default {
name: 'login',
setup () {
return { v$: useVuelidate() }
},
data: () => ({
email: '',
password: ''
}),
validations: {
email: { email, required },
password: { required, minLength: minLength(6) }
},
methods: {
onSubmit () {
if (this.$v.$invalid) {
this.$v.$touch()
return
}
this.$router.push('/')
}
}
}
</script>
Then I try to run all that with npm run serve but with no success. Chrome DeveloperTools inform me about "Uncaught (in promise) TypeError: Cannot read property 'super' of undefined".
What did I do wrong? Is it possible to use Vue3 and Vuelidate together?
Lidia
Step 1: Import useVuelidate inside component
import useVuelidate from "#vuelidate/core";
import { email, required, minLength } from "#vuelidate/validators";
Step 2: Initalize useVuelidate inside component
setup() {
return { v$: useVuelidate() };
},
Step 3: Initalize your modal data
data() {
return {
email: '',
password: ''
};
},
Step 4: Add validations rule
validations() {
return {
email: { email, required },
password: { required, minLength: minLength(6) }
};
},
Step 5: Add form submit method
methods: {
onSubmit: function() {
this.v$.$touch();
if (this.v$.$error) return;
alert('Form is valid')
}
}
Step 6: HTML template design will be like,
<form class="card auth-card" #submit.prevent="onSubmit">
<div class="card-content">
<span class="card-title">Example</span>
<div class="input-field">
<label for="email">Email <span class="required">*</span></label>
<input id="email" type="text" v-model.trim="email">
<small class="error" v-for="(error, index) of v$.email.$errors" :key="index">
{{ capitalizeFirstLetter(error.$property) }} {{error.$message}}
</small>
</div>
<div class="input-field">
<label for="email">Password <span class="required">*</span></label>
<input id="email" type="text" v-model.trim="password">
<small class="error" v-for="(error, index) of v$.password.$errors" :key="index">
{{ capitalizeFirstLetter(error.$property) }} {{error.$message}}
</small>
</div>
</div>
<div class="card-action">
<div>
<button class="btn waves-effect waves-light auth-submit" type="submit"> Enter-login<i class="material-icons right">send</i>
</button>
</div>
</div>
</form>
</template>
DEMO

router doesnt redirect at first attempt

I have a Vue application that is connected to Firebase and is using Vue router. I have a Login page and UserProfile page, when user enters their credentials, I want to redirect them to the UserPage using router, like this:
submit() {
firebase
.auth()
.signInWithEmailAndPassword(this.form.email, this.form.password)
.then(data => {
console.log("data", data);
this.$router.push({ name: "profile" }).catch(err => {
console.log("err");
});
})
.catch(err => {
this.error = err.message;
});
}
The weird thing is, that when I try to login, first attempt displays 'data' in the console and then the 'err" (which is undefined) and it doesnt redirect, but still logs in to user account. When I click submit button again, then it shows 'data' again but no 'err' and redirects to the UserProfile page. I cant figure out why its happening, any help would be appreciated!
The form and button code:
<form action="#" #submit.prevent="submit">
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">Email</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control"
name="email" value required autofocus v-model="form.email"/>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control"
name="password" required v-model="form.password"/>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="submit" class="btn btn-primary">Login</button>
</div>
</div>
</form>
plus submit method:
methods: {
submit() {
firebase
.auth()
.signInWithEmailAndPassword(this.form.email, this.form.password)
.then(data => {
console.log("data", data);
this.$router.push({ name: "profile" }).catch(err => {
console.log("err", err);
});
})
.catch(err => {
this.error = err.message;
});
}
}
Router config (index.js):
Vue.use(Router);
const router = new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "*",
redirect: "/games"
},
{
path: "/games",
name: "games",
component: Games
},
{
path: "/games/:game",
name: "game",
component: Game
},
{
path: "/profile",
name: "profile",
component: Profile,
meta: {
requiresAuth: true
}
},
{
path: "/login",
name: "login",
component: Login
},
{
path: "/register",
name: "Register",
component: Register
}
]
});
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!store.getters.user.loggedIn) {
next({ name: "login" });
} else {
next(); // go to wherever I'm going
}
} else {
next(); // does not require auth, make sure to always call next()!
}
});
export default router;
There are probably some side effects due to the action value. You actually don't need to submit your form, just to call the Firebase signInWithEmailAndPassword() method.
I would change your code as follows:
template:
<form>
<div class="form-group row">
<label for="email" class="col-md-4 col-form-label text-md-right">Email</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control"
name="email" value required autofocus v-model="form.email"/>
</div>
</div>
<div class="form-group row">
<label for="password" class="col-md-4 col-form-label text-md-right">Password</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control"
name="password" required v-model="form.password"/>
</div>
</div>
<div class="form-group row mb-0">
<div class="col-md-8 offset-md-4">
<button type="button" #click="login" class="btn btn-primary">Login</button>
</div>
</div>
</form>
script:
methods: {
login() {
firebase
.auth()
.signInWithEmailAndPassword(this.form.email, this.form.password)
.then(data => {
console.log("data", data);
this.$router.push({ name: "profile" })
});
})
.catch(err => {
this.error = err.message;
});
}
}

State variables becoming undefined when calling vuex action

Error Message:Error: Function DocumentReference.set() called with invalid data. Unsupported field value: undefined (found in field articleName)
I am trying to add the variables from my form to my Firebase Cloud Firestore database. I use vuex for global state management. When I pass the variables from my CreateSale component to sale->state they get defined. But when I call the action createSale when submitting the form, the state variables are undefined. Everything should be setup properly. I registered Vuex aswell as configured firebase.
sale.js (vuex module)
createSale(state, getters) {
console.log(state.articleName) //undefined
db.collection('clothes').add({
articleName: state.articleName,
description: state.description,
brand: state.brand,
price: state.price,
imagesRefs: getters.fileRefs
}).then(docRef => {
for (var i = 0; i < state.files.length; i++) {
}
this.$router.push('/')
}).catch(error => alert(error.message))
}
CreateSale.vue
template:
<div class="container" id="createSale">
<form #submit.prevent="createSale" class="col s12">
<div class="row">
<div class="input-field col s12">
<input type="text" v-model="articleName" v-on:change="setArticleName(articleName)" required>
<label>Article Name</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="text" v-model="description" v-on:change="setDescription(description)" required>
<label>Description</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input type="text" v-model="brand" v-on:change="setBrand(brand)" required>
<label>Brand</label>
</div>
</div>
<div class="row">
<div class="input-field col s12">
<input style="padding-top: 16px;" type="number" v-model="price" v-on:change="setPrice(price)" required>
<label>Price</label>
</div>
</div>
<UploadFile />
<button type="submit" class="btn btn-success">Submit</button>
<router-link to="/" class="btn grey">Cancel</router-link>
</form>
script:
export default {
name: 'createSale',
components: {
UploadFile
},
data() {
return {
articleName: '',
description: '',
brand: '',
price: ''
}
},
methods: {
...mapMutations('sale', [
'setArticleName',
'setDescription',
'setBrand',
'setPrice'
]),
...mapActions('sale', {
createSale: 'createSale',
console: 'console'
})
}
Your action need to be
createSale({state, getters}) {
console.log(state.articleName) //undefined
db.collection('clothes').add({
articleName: state.articleName,
description: state.description,
brand: state.brand,
price: state.price,
imagesRefs: getters.fileRefs
}).then(docRef => {
for (var i = 0; i < state.files.length; i++) {
}
this.$router.push('/')
}).catch(error => alert(error.message))
}
The first parameter which is passed to action is context object, you need to get state and getters from this context object.
Example from vuex website:

I want to be able to set a "name" for a user in Firebase when using .createUserWithEmailAndPassword

VueJS, with Vuex and Firebase allow me to to register a user in my app easily but can I, AT THE SAME TIME, create one or more database refs for that specific user?
I can set and get the user.email from the users list, but I'm stuck when it comes to create more fields in the actual database.
Can you help me out?
This is my sign-up component code:
<template>
<section class="section">
<h1>Sign-up</h1>
<h2>Create an Account</h2>
</div>
<div class="card-content">
<form v-on:submit.prevent>
<div class="field">
<label class="label">Name</label>
<div class="control">
<input class="input" type="text" placeholder="Your Display Name" v-model="name">
</div>
</div>
<div class="field">
<label class="label">Email</label>
<div class="control">
<input class="input" type="email" placeholder="joe#bloggs.com" v-model="email">
</div>
</div>
<div class="field">
<label class="label">Password</label>
<div class="control">
<input class="input" type="password" v-model="password">
</div>
</div>
<button type="submit" class="button is-primary" v-on:click="signUp">Sign-up</button>
</form>
<p>Already registered? <router-link to="/Sign-In">Log in</router-link></p>
</section>
</template>
<script>
import Firebase from "firebase";
export default {
data: function() {
return {
name: "",
email: "",
password: ""
};
},
methods: {
signUp: function() {
Firebase.auth()
.createUserWithEmailAndPassword(this.email, this.password)
.then(
user => {
this.$router.replace('dashboard');
},
error => {
alert(error.message);
}
);
}
}
};
</script>
I am assuming I could create another method but I really want to keep it as simple as possible.
You need to make an additional API call to set the displayName on the user:
firebase.auth()
.createUserWithEmailAndPassword(this.email, this.password)
.then(
user => {
return user.updateProfile({displayName: this.displayName});
},
error => {
alert(error.message);
}
);

Resources