Vue js with firebase not switching pages [duplicate] - javascript

This question already has answers here:
Is there something to auto update this code in vue?
(2 answers)
Closed 2 years ago.
So my problem is that when I log in with firebase and my mounted function should switch the component, I tried a lot of stuff, and nothing works. I just need a way to with what my router will switch the page after I sign in.
<template>
<div class="vue-tempalte" id="regForm">
<form>
<h1 id="middle">Sign In</h1>
<div class="form-group">
<label>Email address</label>
<input type="email" id="email" v-model="email" required />
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="password" v-model="password" required />
</div>
<button type="submit" #click="login" class="button is-light" id="btn1">
Sign In
</button>
</form>
</div>
</template>
<style scoped>
</style>
<script>
import firebase from "firebase";
export default {
name: "login",
data() {
return {
email: "",
password: "",
};
},
mounted: function () {
if (firebase.auth().currentUser) this.$router.replace("/HeaderLoggedIn");
},
methods: {
login: function () {
firebase
.auth()
.signInWithEmailAndPassword(this.email, this.password)
.then((user) => {
console.log(user.user);
});
},
},
};
</script>

The problem is in this code:
mounted: function () {
if (firebase.auth().currentUser) this.$router.replace("/HeaderLoggedIn");
},
Right now you're checking whether the user is signed in when the component is mounted. This happens once, while the user being authenticated at many times, since it is an asynchronous operatation.
You'll want to instead *listen for authentication state changes, as shown in the first snippet in the documentation on determining the signed in user:
mounted: function () {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.$router.replace("/HeaderLoggedIn");
}
});
},

Instead of this method, you can try to set a global router.beforeEach method in your main.js file and check the auth state before entering each page and all in one place, not separate for each page then you can do something based on each state.

Related

How can I create a reusable form component for each resource I create and/or update (Vue 3, vue-router, Pinia)

I have a Vue 3 app using Pinia stores that CRUD's data from my rest API. I've just started working with Vue 3 (from smaller vue 2 projects) and this is my first time using Pinia, so I'm still learning the intricacies of both.
One resource I manage from my api is called Applications, and I have a composable that manages API calls to retrive all apps, 1 app, or update the selected app. Instead of creating a form component to UPDATE, and a form component to CREATE applications, I'd like to create a single form component that handles both. So far I can populate my form with an existing application using a route that contains an application_id, and I create a new application if no application_id is in my route.params. I'm just not sure how to tell the form "Hey lets update this application instead of creating it.". I thought of using v-if directives that each create a <button> (one to run update, one to run create method) depending on there is an application_id in my route.params, but that seems inefficient (it may be correct, I'm just lacking knowledge). Here's my code:
// ApplicationStore.js (pinia store)
import { defineStore } from "pinia";
// Composable for axios API calls
import { getApplications, getApplicationByID, createApplication } from "#/composables/applications";
export const useApplicationStore = defineStore("application", {
state: () => ({
applications: [], //list of applications from database
application: {}, //currently selected application for edit form
loading: false,
success: "Successfully Created",
error: "",
}),
getters: {},
actions: {
async fetchApplications() {
this.loading = true;
this.applications = [];
const { applications, error } = await getApplications();
this.applications = applications;
this.error = error;
this.loading = false;
},
async fetchApplicationByID(id) {
this.loading = true;
const { application, error } = await getApplicationByID(id);
this.application = application;
this.error = error;
this.loading = false;
},
async createNewApplication() {
this.loading = true;
const { application, results, error } = await createApplication(this.application);
this.application = application;
this.error = error;
this.loading = false;
if (results.status === 201) {
// show this.success toast message
}
}
}
});
Here is my ApplicationForm component. It currently looks for route.param.id to see if an application is selected, if so it populates the form:
// ApplicationForm.vue
<template>
<section class="columns">
<div class="column">
<div v-if="error" class="notification is-danger">{{ error }}</div>
<div class="field">
<label class="label">Name</label>
<input v-model="application.name" class="input" type="text" />
</div>
<div class="field">
<label class="label">Location</label>
<input v-model="application.location" class="input" type="text" />
</div>
<div class="control">
<button #click="createNewApplication" class="button">Save</button>
</div>
</div>
</section>
</template>
<script setup>
import { useRoute } from "vue-router";
import { useApplicationStore } from "#/stores/ApplicationStore";
import { storeToRefs } from "pinia";
const route = useRoute();
const { applications, application, error } = storeToRefs(useApplicationStore());
const { createNewApplication } = useApplicationStore();
//checking if there's an id parameter, if so it finds the application from the list in the store
if (route.params.id) {
application.value = applications.value.find(app => app.id === Number(route.params.id));
} else {
//form is blank
application.value = {};
error.value = "";
}
</script>
Is there a preferred way to use this single form for both create and updates? I wonder if slots would be a good use case for this? But then I think I'd still end up making multiple form components for each CRUD operation. Also, I considered using a v-if to render the buttons based on if an application is in the store or not, like this:
<button v-if="route.params.id" #click="updateApplication" class="button">Update</button>
<button v-else #click="createNewApplication" class="button">Save</button>
I can't help but feel there is a better way to handle this (it is something I'll utilize a lot in this and future projects). This is my first big vue/pinia app. I'm loving the stack so far but these little things make me question whether or not I'm doing this efficiently.
If the form's UI is mainly expected to stay the same except for a few small differences (e.g. the button text), you could make the form emit a custom "submit" event and then handle that event from the parent component where you render the form (i.e. on the update page you have <ApplicationForm #submit="updateApplication"> and on the create page you have <ApplicationForm #submit="createNewApplication" />:
// ApplicationForm.vue
<template>
<section class="columns">
<div class="column">
<div v-if="error" class="notification is-danger">{{ error }}</div>
<div class="field">
<label class="label">Name</label>
<input v-model="application.name" class="input" type="text" />
</div>
<div class="field">
<label class="label">Location</label>
<input v-model="application.location" class="input" type="text" />
</div>
<div class="control">
<button #click="$emit('submit')" class="button">{{ buttonText }}</button>
</div>
</div>
</section>
</template>
As for the text, you can pass that as a prop (e.g. buttonText) to the ApplicationForm component. If some sections of the form are more substantially different than just different text between the "Update" and "Create" form, that's when you'd use slots.
I wouldn't recommend making the <ApplicationForm /> component responsible for reading the route parameters; that should generally be done only by the Vue component responsible for rendering the page (and then it should pass that data through props so that the component is as re-usable as possible)
So your parent component could look something like this:
<ApplicationForm v-if="application" #submit="updateApplication" />
<ApplicationForm v-else #submit="createNewApplication" />

InertiaJS keep form data after validation error

In my InertiaJS/VueJS project I have a prop that receive some data from the backend:
event: {
type: Object,
default: () => { return {} }
},
That's how the event obj looks in the backend:
['name' => 'Event Name']
I use toRefs to convert the reactive prop and update its properties in the UI:
const eventRef = toRefs(props).event
So the Event has the name 'Event Name' when the component loads, when I update the event name in the UI to 'New Name' and submit the form, I send the eventRef obj in the request to create the new event:
Inertia.post(url, eventRef, only: ['global'])
If there's a validation error in the backend, I return it to the frontend and show the error in the UI (This is working without problems). The problem I have is that Inertia (or maybe VueJS) is returning the object eventRef to his previous state when the component is created. Which means that the name property of the eventRef changes to 'Event Name' again, instead of staying with 'New Name` that was updated in the UI. I would like to preserve the state of the object after I submit the form. This is my Inertia response:
component: "Events/EventNew"
props: {
global: {} // Global object
}
url: "/app/qa/events/new"
version: null
As you can see I'm not even getting the 'event' prop from the backend, so it shouldn't be updated. After reading Inertia docs I thought that a simple preserveState:true in the request options would do the job but this is not happening. Every time the server returns an Inertia response, the eventRef obj is 'reset'.
What am I missing here? I would appreciate some help
I believe I had the same problem using Inertia with Vue2. If I understood correctly, you probably seeing this on a form where you trying to update and entry, right? Your validation is working but the form keeps resetting itself to the previous state. If that's the case, what solved this for me was this:
Instead of using Inertia.post() directly, use the Inertia Form Helper instead
Vue 2
<template>
<form #submit.prevent="form.post('/login')">
<!-- email -->
<input type="text" v-model="form.email">
<div v-if="form.errors.email">{{ form.errors.email }}</div>
<!-- password -->
<input type="password" v-model="form.password">
<div v-if="form.errors.password">{{ form.errors.password }}</div>
<!-- remember me -->
<input type="checkbox" v-model="form.remember"> Remember Me
<!-- submit -->
<button type="submit" :disabled="form.processing">Login</button>
</form>
</template>
<script>
export default {
data() {
return {
form: this.$inertia.form({
email: null,
password: null,
remember: false,
}),
}
},
}
</script>
Vue 3
<template>
<form #submit.prevent="form.post('/login')">
<!-- email -->
<input type="text" v-model="form.email">
<div v-if="form.errors.email">{{ form.errors.email }}</div>
<!-- password -->
<input type="password" v-model="form.password">
<div v-if="form.errors.password">{{ form.errors.password }}</div>
<!-- remember me -->
<input type="checkbox" v-model="form.remember"> Remember Me
<!-- submit -->
<button type="submit" :disabled="form.processing">Login</button>
</form>
</template>
<script>
import { useForm } from '#inertiajs/inertia-vue3'
export default {
setup () {
const form = useForm({
email: null,
password: null,
remember: false,
})
return { form }
},
}
</script>
I solved the problem, it was the toRefs that was modifying the props in the component after the request was sent. Using a reactive object was the solution:
const eventRef = reactive(props.event)

Dynamically display fetched data in input field using Laravel 8 Vue Js

I have a simple registration form in Laravel 8 using Vue js where I need to check first if the user who refers the person registering exists in my database prior to submitting. if a record exists, I need to dynamically display the user's full name in the input field on the #change event.
Here's my Vue component:
<template>
<div>
<h2>TESTING</h2>
<form #submit.prevent="submit" >
<input type="text" class="form-control" v-model="form.ucode" #change="getUser()">
<input type="text" class="form-control" v-model="form.uname">
</form>
</div>
</template>
<script>
export default {
data: function(){
return{
form: {
ucode: "",
uname: "",
},
}
},
methods:{
getUser(){
axios.get('api/checkUser?ucode=' + this.form.ucode).then(res=>{
this.form.uname = res.data.first_name
})
}
}
}
Here's my ResellerController and API route:
Route::get('/checkUser', [ResellerController::class, 'show']);
public function show()
{
$ucode = request('ucode');
$user = DB::table('resellers')->where('username', $ucode)->select('id', 'first_name')->get();
return response()->json($user);
}
I think I don't have issues with my controller because it returns back the correct JSON data
[{"id":1,"first_name":"William Hardiev"}]
But when I test my code, uname is undefined.
form:Object
ucode:"williambola_05"
uname:undefined
Can anyone help me with this?
You issue is the JSON response that you receive from the server. You are getting a JSON Array from the server, whereas your JS code is handling a JSON object
You can handle it like this:
<template>
<div>
<h2>TESTING</h2>
<form #submit.prevent="submit">
<input
type="text"
class="form-control"
v-model="form.ucode"
#change="getUser()"
/>
<input type="text" class="form-control" v-model="form.uname" />
</form>
</div>
</template>
<script>
import axios from "axios";
export default {
data: function() {
return {
form: {
ucode: "",
uname: ""
}
};
},
methods: {
getUser() {
axios.get("api/checkUser/?ucode=" + this.form.ucode).then(res => {
this.form.uname = res.data[0].first_name;
});
}
}
};
</script>
OR you can just change the get query on the server side to simply return a single JSON object rather than an array and your js code should automatically start working:
$user = DB::table('resellers')
->where('username', $ucode)
->select('id', 'first_name')
->first();

Can't Submit Axios Post form Nuxt.js (VueJS)

I'm playing around with my first ever form in Vue. I've created my app with Nuxt.
I'm able to get data via an axios get request from my API but I can't seem to post data.
new.vue
<template>
<section class="container">
<div>
<h1>Gins</h1>
<form #submit.prevent="addGin">
<h4>New Product</h4>
<p>
<label for="name" class="input-label">Title:</label>
<input id="name" v-model="title" type="text" name="name" class="input">
</p>
<p>
<button type="submit" value="Submit" class="button">Add Gin</button>
</p>
</form>
</div>
</section>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
title: '',
errors: []
}
},
methods: {
addGin() {
axios.post('/apv/v1/gins', this.title)
.then((Response) => {})
.catch((err) => {
this.errors.push(err)
})
}
}
}
</script>
When clicking the submit button, I'm not receiving any errors, but I can confirm no entry is added to my API database.
My API is running on a different server localhost:4000 and I have set up the proxy in nuxt.config.js
axios: {
proxy: true
},
proxy: {
'/api/v1/': 'http://localhost:4000'
},
I've experimented with both <form #submit.prevent="addGin"> and <form v-on:submit.prevent="addGin"> but this doesn't seem to make a difference.
What else might I be missing?
Add #nuxtjs/axios module into modules part of nuxt.config
Use this.$axios instead of imported one. Proof: https://axios.nuxtjs.org/usage
OK so was really close. Changing my axios params to title: this.title, apparently did the trick.

The worlds simplest vue.js/vuefire/firebase app. But can't read value

Vue.js is a bit of a life style change for a sql programmer, but getting there. Here I use vuefire as per specs, even manually inserting the correct key from my one record, one field Firebase DB to test it out. Yet see nothing returned or displayed.
Love to know how to do that! And fill in my one value form. From there the sky's the limit I'm sure. Thanks for any help.
ERROR MESSAGE: vue.common.js?e881:481 [Vue warn]: Error in mounted hook: "ReferenceError: residentsArray is not defined"
<template>
<div class="edit">
<div class="container">
<p style="margin-bottom:10px;font-weight:800;">EDIT RECORD</p><br>
<form id="form" v-on:submit.prevent="updateResident">
<fieldset class="form-group">
<label for="first_name">Name</label>
<input type="text" class="form-control" id="first_name" name="first_name" v-model="currentResident.name">
</fieldset>
<input type="submit" class="btn btn-primary" value="Add New Resident">
</form>
</div>
</div>
</template>
<script>
import firebase from 'firebase'
let editConfig = {
apiKey: "123456789",
authDomain: "myapp.firebaseapp.com",
databaseURL: "https://myapp.firebaseio.com",
projectId: "my",
storageBucket: "myapp.appspot.com",
messagingSenderId: "1234567890"
};
var firebaseApp = firebase.initializeApp(editConfig, "Edit")
var db = firebaseApp.database()
let residentsReference = db.ref('residents')
export default {
name: 'Edit',
mounted() {
this.currentResident.name = residentsArray.name;
},
firebase: {
residentsArray: residentsReference.child('-KpzkbA6G4XvEHHMb9H0')
},
data () {
return {
currentResident: {
name: ''
}
}
},
methods: {
updateResident: function () {
// Update record here
}
}
}
</script>
You should change residentsArray.name to this.residentsArray.name in your mounted hook.
One more thing: You don't need mounted for this.
I'd just make a computed for this which return this.residentsArray.name. With this, you won't have any binding problems.
From reading the README on the vuefire Github, it looks like you need to refer the properties of your firebase object via this.$firebaseRefs:
mounted() {
this.currentResident.name = this.$firebaseRefs.residentsArray.name;
},

Categories