Conditional navbar based on a user authentication status - firebase

I'm trying to conditionally display navbar elements of a navigation component based on the onAuthStateChanged Firebase function.
<template>
<navbar dark position="top" class="default-color" scrolling>
<mdb-navbar-brand href="#/" style="font-weight: bolder;">
Test
</mdb-navbar-brand>
<navbar-collapse>
<navbar-nav left>
<navbar-item href="#/" waves-fixed>Home</navbar-item>
<navbar-item href="#/css" waves-fixed>About</navbar-item>
<navbar-item href="#/jobs" waves-fixed>Jobs</navbar-item>
<navbar-item href="#/advanced" waves-fixed>Profile</navbar-item>
</navbar-nav>
<navbar-nav right>
<router-link to="/signup"><button v-if="!user" type="button" class="btn btn-primary">Signup</button></router-link>
<router-link to="/login"><button v-if="!user" type="button" class="btn btn-primary">Login</button></router-link>
<p><a v-if="user" #click="logout">Logout</a></p>
</navbar-nav>
</navbar-collapse>
</navbar>
</template>
<script>
import Navbar from '#/components/Navbar.vue';
import NavbarItem from '#/components/NavbarItem.vue';
import NavbarNav from '#/components/NavbarNav.vue';
import NavbarCollapse from '#/components/NavbarCollapse.vue';
import mdbNavbarBrand from '#/components/NavbarBrand.vue';
import firebase from 'firebase';
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
logout() {
firebase.auth().signOut()
.then(() => {
this.$router.push({path: '/'});
});
},
created() {
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
}
};
</script>
Unfortunately, for some reason, the onAuthStateChanged is not working. I also tried to simply display the user in the console from the component perspective, but it's not working as well:
console.log(firebase.auth().currentUser);
Thanks in advance for any hints.

I just wanted to point out another option. Renaud Tarnec's answer is correct but there is a second solution.
You can use the arrow function syntax. With arrow functions the context doesnt change so there is no need to set vm = this before the function since this will still work inside the function. I'm a huge fan of lambda/arrow functions and see no reason not to use them.
Renaud Tarnec's should be the accepted answer but just wanted to offer a second option :)
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.user = user;
} else {
this.user = null;
}
});
}
};

If you want to call firebase.auth().onAuthStateChanged() in the created lifecycle hook you should do as follows:
export default {
name: 'Navigation',
data() {
return {
user: null,
};
},
components: {
Navbar,
NavbarItem,
NavbarNav,
NavbarCollapse,
mdbNavbarBrand
},
methods: {
....
}
},
created: function () {
var vm = this;
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
vm.user = user;
} else {
vm.user = null;
}
});
}
};
The way you do it, you are declaring created as a "standard" component method.

Related

Composition API - Axios request in setup()

I am experimenting with Vue3's Composition API in a Laravel/VueJS/InertiaJS stack.
A practice that I have used a lot in Vue2 with this stack is to have 1 route that returns the Vue page component (eg. Invoices.vue) and then in the created() callback, I would trigger an axios call to an additional endpoint to fetch the actual data.
I am now trying to replicate a similar approach in Vue3 with composition API like so
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: getInvoices(),
selectedInvoices: [],
});
async function getInvoices() {
loading.value = true;
return await axios.get(props.fetch_url).then(response => {
return response.data.data;
}).finally(() => {
loading.value = false;
})
}
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
This however keeps on giving me the propise, rather than the actual data that is returned.
Changing it like so does work:
export default {
components: {Loader, PageBase},
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: [],
selectedInvoices: [],
});
axios.get(props.fetch_url).then(response => {
state.invoices = response.data.data;
}).finally(() => {
loading.value = false;
})
function handleSelectionChange(selection) {
state.selectedInvoices = selection;
}
return {
loading,
state,
handleSelectionChange,
}
}
}
I want to use function though, so I can re-use it for filtering etc.
Very curious to read how others are doing this.
I have been googling about it a bit, but cant seem to find relevant docu.
All feedback is highly welcomed.
I tried this now with async setup() and await getInvoices() and <Suspense> but it never displayed any content.
So this is how I'd do it, except I wouldn't and I'd use vuex and vuex-orm to store the invoices and fetch the state from the store.
<template>
<div>loading:{{ loading }}</div>
<div>state:{{ state }}</div>
</template>
<script>
import {defineComponent, ref, reactive} from "vue";
import axios from "axios";
export default defineComponent({
name: 'HelloWorld',
props: {
fetch_url: {
required: true,
type: String,
}
},
setup(props) {
const loading = ref(false)
const state = reactive({
invoices: []
})
async function getInvoices() {
loading.value = true;
await axios.get(props.fetch_url).then(response => {
state.invoices = response.data;
}).finally(() => {
loading.value = false;
})
}
return {
getInvoices,
loading,
state,
}
},
async created() {
await this.getInvoices()
}
})
</script>
<style scoped>
</style>
This is of course similar to what you're doing in option 2.

img not loading from :src

I am getting the image location from firestore and would like to show the image using v-bind:src. You can find my code below:
<b-avatar v-bind:src = "profilepic" class="mr-5" size="8em"></b-avatar>
my methods can be found below:
export default {
data() {
return {
uid: "",
profilepic: "",
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL().then(imgURL => {
this.profilepic = imgURL;
alert(this.profilepic); // shows the correct path
})
},
}
created() {
this.uid = fb.auth().currentUser.uid;
this.getprofilepic();
}
}
I am confident that this.profilepic is storing the correct path as if i were to manually type in the path, it will show. I am suspecting that the page loaded before path could be retrieve from firestore. How can i work around this? Thank you in advance.
I have tried hardcoding the path directly to the data and it works fine. The code can be found below:
data() {
return {
uid: "",
profilepic: "*my firebase storage path*",
}
},
With that im not really sure why isnt it still showing
In the script below the template tags, you need to make sure to include the image, and of course, instead of putting my path, put your image's path!
<script>
export default {
data() {
return {
files: {
my_pic: require('/src/assets/img/avatars/logo.png')
}
};
},
}
};
</script>
Then in your and where you want to put your image, you need to put it in this format
<img :src="files.my_pic">
Let me know if this helps or if you want me to expand more.
Try waiting for the uuid to get retrieved:
<template>
<img height="200" v-bind:src = "profilepic" />
</template>
<script>
export default {
data() {
return {
uuid: undefined,
profilepic: undefined
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL()
.then(imgURL => {
this.profilepic = imgURL;
})
},
getuuid(){
return new Promise((resolve, reject) => {
var user = fb.auth().currentUser;
if(user == null) reject()
if (user) resolve(user.uid)
})
}
},
created() {
this.getuuid()
.then(uuid => this.uuid = uuid)
.then(() => {
this.getprofilepic();
})
}
};
</script>
As you can see in this example, it does not matter how long it takes for the URL to load: Vue SFC Playground
When Vue Loader compiles the <template> blocks in SFCs, it also converts any encountered asset URLs into webpack module requests.
For example, the following template snippet:
<img src="../image.png">
will be compiled into:
createElement('img', {
attrs: {
src: require('../image.png') // this is now a module request
}
})
By default the following tag/attribute combinations are transformed, and can be configured using the transformAssetUrls option.
{
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
Step 1: Create vue.config.js
module.exports = {
productionSourceMap: false,
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.loader('vue-loader')
.tap(options => {
options['transformAssetUrls'] = {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href',
'b-avatar': 'src',
'b-img': 'src',
'b-img-lazy': ['src', 'blank-src'],
'b-card': 'img-src',
'b-card-img': 'src',
'b-card-img-lazy': ['src', 'blank-src'],
'b-carousel-slide': 'img-src',
'b-embed': 'src'
}
return options
})
}
}
Step 2: Inside main.js import vue.config
import '../vue.config'
Step 3: Create your html template
<template>
<b-avatar :src="profilepic" class="mr-5" size="8em"></b-avatar>
</template>
<script>
import { BAvatar } from 'bootstrap-vue'
export default {
name: 'bootstrap-image-avatar',
components: {
'b-avatar': BAvatar
},
data() {
return {
uid: "",
profilepic: "",
}
},
methods: {
getprofilepic() {
fb.storage().ref('users/' + this.uid + '/profile.jpg').getDownloadURL().then(imgURL => {
this.profilepic = imgURL;
alert(this.profilepic); // shows the correct path
})
},
}
created() {
this.uid = fb.auth().currentUser.uid;
this.getprofilepic();
}
}
</script>

React props using Meteor Apollo

I am playing with the Meteor Apollo demo repo.
I am having difficulty passing variables down to children with React. I am getting an error
imports/ui/Container.jsx:10:6: Unexpected token (10:6)
The below code is the Container.jsx component:
import React from 'react';
import { Accounts } from 'meteor/std:accounts-ui';
class Container extends React.Component {
render() {
let userId = this.props.userId;
let currentUser = this.props.currentUser;
}
return (
<Accounts.ui.LoginForm />
{ userId ? (
<div>
<pre>{JSON.stringify(currentUser, null, 2)}</pre>
<button onClick={() => currentUser.refetch()}>Refetch!</button>
</div>
) : 'Please log in!' }
);
}
}
It is passed props via the Meteor Apollo data system (I have omitted some imports at the top):
const App = ({ userId, currentUser }) => {
return (
<div>
<Sidebar />
<Header />
<Container userId={userId} currentUser={currentUser} />
</div>
)
}
// This container brings in Apollo GraphQL data
const AppWithData = connect({
mapQueriesToProps({ ownProps }) {
if (ownProps.userId) {
return {
currentUser: {
query: `
query getUserData ($id: String!) {
user(id: $id) {
emails {
address
verified
}
randomString
}
}
`,
variables: {
id: ownProps.userId,
},
},
};
}
},
})(App);
// This container brings in Tracker-enabled Meteor data
const AppWithUserId = createContainer(() => {
return {
userId: Meteor.userId(),
};
}, AppWithData);
export default AppWithUserId;
I would really appreciate some pointers.
I believe the error is that you accidentally ended the render function before the return statement.
render() { // <- here it starts
let userId = this.props.userId;
let currentUser = this.props.currentUser;
} // <- here it ends
Another error is that your return statement doesn't return a single DOM element, but two of them: an Accounts.ui.LoginForm and a div. The return function should only return one element. Just put the entire thing into a single <div>.

Two-Way Data Binding with React and Meteor

Is there another way of writing this? By the way, this works perfectly but I feel it could be written better:
Profile = React.createClass({
mixins: [ReactMeteorData],
getMeteorData() {
return {
currentUser: Meteor.user(),
};
},
getInitialState(){
// we add an if statement to prevent undefined errors
// could this be written elsewhere?
if (Meteor.user().profile) {
this.profile = Meteor.user().profile;
} else {
this.profile = '';
};
return { firstname: this.profile.firstname };
},
...
)};
Yes, you can use an ES6 class, which is the way recommended when working with Meteor (ecmascript package has you covered). Also, you don't need to use Meteor.user() outside getMeteorData() method.
class Profile extends React.Component {
constructor(props){ super(props); }
mixins: [ReactMeteorData]
getMeteorData() {
return {
user: Meteor.user(),
};
}
getInitialState(){
return {
firstname: this.data.user && this.data.user.profile.firstname
}
}
}

Vuex state change is not reactive

I am working with Vuex and Firebase Auth system.
I just want to store with Vuex the user object that i get from:
firebase.auth().getCurrentUser
so that every time it changes, it updates the views.
But i ve troubles with this.
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {
loggedIn: false,
data: null
}
},
getters: {
user(state){
return state.user
}
},
mutations: {
SET_LOGGED_IN(state, value) {
state.user.loggedIn = value;
},
SET_USER(state, data) {
state.user.data = data;
}
},
actions: {
fetchUser({ commit }, user) {
commit("SET_LOGGED_IN", user !== null);
if (user) {
commit("SET_USER", user);
} else {
commit("SET_USER", null);
}
}
}
});
Account.vue
<template>
<ion-item>
<ion-label #click="openModal()" position="stacked">Your name</ion-label>
{{user.data.displayName}}
</ion-item>
</template>
computed: {
// map `this.user` to `this.$store.getters.user`
...mapGetters({
user: "user"
})
},
methods: {
openModal() {
let us = this.$store.getters.user;
return this.$ionic.modalController
.create({
component: Modal,
componentProps: {
data: {
content: 'New Content',
},
propsData: {
pro: us.data.displayName
},
},
})
.then(m => m.present())
},
.
.
.
</script>
Modal.vue
<template>
<ion-app>
<h1>MODAL</h1>
<ion-input :value="prop" #input="prop = $event.target.value"></ion-input>
<ion-button #click="clos()">Save</ion-button>
</ion-app>
</template>
<script>
import firebase from "firebase";
export default {
props:['pro'],
data(){
return{
prop: this.pro
}
},
methods:{
clos(){
let vm = this;
let user = firebase.auth().currentUser;
window.console.log("updating",vm.prop)
user.updateProfile({
displayName: vm.prop
}).then(function(){
user = firebase.auth().currentUser;
vm.$store.dispatch("fetchUser",user);
}).catch(function(err){
window.console.log("err",err);
})
this.$ionic.modalController.dismiss();
}
}
}
</script>
I can see using Vue Dev Tools that when I dispatch the new user in Modal.vue
vm.$store.dispatch("fetchUser",user);
that the Vuex state is correctly updated, but the view in Account.vue is not.
But if I press the button 'commit this mutation' in the dev tools the view updates!
How can I fix this behavior?
try this solution:
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {
loggedIn: false,
data: null
}
},
getters: {
user(state){
return state.user;
},
userData(state){
return state.user.data;
}
},
mutations: {
SET_LOGGED_IN(state, value) {
state.user.loggedIn = value;
},
SET_USER(state, data) {
state.user.data = data;
}
},
actions: {
fetchUser({ commit }, user) {
commit("SET_LOGGED_IN", user !== null);
if (user) {
commit("SET_USER", user);
} else {
commit("SET_USER", null);
}
}
}
});
Account.vue
<template>
<ion-item>
<ion-label #click="openModal()" position="stacked">Your name</ion-label>
{{user.displayName}}
</ion-item>
</template>
computed: {
// map `this.user` to `this.$store.getters.user`
...mapGetters({
user: "userData"
})
},
methods: {
openModal() {
let us = this.$store.getters.userData;
return this.$ionic.modalController
.create({
component: Modal,
componentProps: {
data: {
content: 'New Content',
},
propsData: {
pro: us.displayName
},
},
})
.then(m => m.present())
},
.
.
.
</script>
You can try this:
SET_USER(state, data) {
Vue.$set(state.user, 'data', data)
}

Resources