Firestore onSnapshot duplicates all DOM elements instead of replacing/updating them - firebase

I am updating the information in my database and I want that data to update the Cards I am using to display the data RealTime. This works fine but when I make a change to the database the cards I am using to display the info just duplicate on every change made.
I Want it to replace/update the Cards I am displaying But it makes a new card and adds it to the screen along with all old cards on every change.
Pretty new to Vue and Firebase cant seem to get it to work.
<template>
<v-flex xs12 sm6 md4 lg3 xlg3 v-for="user in users" :key="user.id">
<v-card raised :class="`${user.Status} text-xs-center ma-3`" >
<v-responsive align="middle" class="pt-4">
<v-icon :class=" `${user.Status} ma-n1 mb-n1`" size="55px" >account_box</v-icon>
</v-responsive>
<v-card-text>
<div align="middle" class="subheading White">{{user.Name}}</div>
<div align="middle" class="grey--text ma-1">
<v-chip color="" small ripple :class="`${user.Status} caption my-3`"><div class="White mr-2">Status: </div><div :class="`${user.Status}`"> {{user.Status}}</div> </v-chip>
</div>
<v-divider></v-divider>
<div align="middle" class="White ma-1">Unit: {{user.GroupHex}} - {{user.UserIDInt}} </div>
<div align="middle" class="grey--text mt-3">Last Message:<div class="Green">{{user.TimeStamp}}</div></div>
</v-card-text>
<v-card-actions raised align="center">
<v-spacer></v-spacer>
<router-link class="noLink" :to="{ path: '/Logs'}" append>
<v-btn color=" " small align="middle" flat class="" >
<v-icon color="grey" class="mr-2">assessment</v-icon>
<span>Log Data</span>
</v-btn>
</router-link>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
<NewEmployee />
</v-container>
</v-app>
</template>
<script>
import firestore from "firestore";
// eslint-disable-next-line
import EmployeePop from "#/components/updatePopups/EmployeePop";
import NewEmployee from "#/components/updatePopups/NewEmployee";
import db from "#/components/fbInit";
import firebase from "firebase";
export default {
// eslint-disable-next-line
components: {EmployeePop, NewEmployee},
data() {
return {
users: [],
links: [
{ route: "/logs" },
]
};
},
created() {
var user = firebase.auth().currentUser;
var employeeRef = db.collection('userProfiles').doc(user.uid).collection('employees')
employeeRef.onSnapshot(snap => {
snap.forEach(doc => {
const data = {
id: doc.id,
Name: doc.data().Name,
GroupHex: doc.data().GroupHex,
UserIDInt: doc.data().UserIDInt,
SuperID: doc.data().SuperID,
misc: doc.data().misc,
Status: doc.data().Status,
TimeStamp: doc.data().TimeStamp,
Original: doc.data().Original
}
this.users.push(data);
});
})
},
</script>
Duplicates v-cards instead of replacing with new data.

This is because each time a document is added to the collection the listener receives the entire set of documents of the collection and you push those documents to the users array without re-initializing the array.
You can verify that by adding a console.log() as follows, which will log the snapshot's size:
employeeRef.onSnapshot(snap => {
console.log(snap.size); <-- Add here
snap.forEach(doc => {...})
//...
});
You could re-initialize the users array each time the listener is triggered, as follows:
employeeRef.onSnapshot(snap => {
this.users = []; <-- re-initialize the array
snap.forEach(doc => {...})
//.....
});
But there is a better solution, as explained in the doc: to analyse "the actual changes to query results between query snapshots".
For example, for catching only the documents that are added to the collection, you would do as follows:
created() {
const user = firebase.auth().currentUser;
const employeeRef = db.collection('userProfiles').doc(user.uid).collection('employees')
employeeRef.onSnapshot(snap => {
snap.docChanges().forEach(change => {
if (change.type === "added") {
const data = {
id: change.doc.id,
Name: change.doc.data().Name,
....
};
this.users.push(data);
}
});
});
}

Related

how can i order by snapshot's child data containing a timestamp index with vue js

I saved the timestamp as a data field inside firebase DB, this way I can now retrieve it like {{ post.timestamp }} to display it, how would I go from where I am at to order the posts by timestamp order regardless of the user object order, for example, what I get in the UI is the posts ordered by the user and not by time.
data on firebase looks like this:
Code looks like this :
<template>
<div>
<div
v-for="post in allPosts.slice().reverse()"
:key="post._key">
<v-card class=" feed-card my-3">
<v-row no-gutters>
<v-col cols="1">
<v-img
class="align-center rounded-xl "
width="30"
:src="post.photoURL">
</v-img>
</v-col>
<v-col cols="10">
<p class="">{{post.postText}}</p>
<p class="blue-grey--text ">{{post.displayName}}</p>
<p class="mb-n1 mt-n5 d-flex flex-row-reverse"> {{post.date}} {{post.time}}</p>
</v-col>
</v-row>
</v-card>
</div>
</div>
</template>
<script>
import firebase from '#/plugins/firebase'
let db = firebase.database();
//let usersRef = db.ref('users');
let postRef = db.ref('posts');
export default {
name: 'FintechSocialFeed',
data: () => ({
authUser: null,
allPosts: [] // initialise an array
}),
methods: {
},
created: function() {
data => console.log(data.user, data.credential.accessToken)
firebase.auth().onAuthStateChanged(user => {
if (user) {
postRef.on('value', snapshot => {
const val = snapshot.val()
if (val) {
this.allPosts = Object.values(val).flatMap(posts =>
Object.entries(posts).map(([ _key, post ]) => ({ _key, ...post})))
}
console.log(snapshot.val())
});
}
})
}
}
</script>
here is the UI showing the latest post at the bottom because it is sorting by the user and not date:
I don't use firebase, but it looks like db reference provides orderByKey, so...
let postRef = db.ref('posts').orderByKey('timestamp');
An alternative would be sorting yourself, after retrieval...
this.allPosts = Object.values(val).flatMap(posts =>
Object.entries(posts).map(([ _key, post ]) => ({ _key, ...post}))
).sort((a, b) => a.timestamp.toMillis() - b.timestamp.toMillis());

Fetch and display lists of an user

I have a profile page that displays the user info. The page shows the user name / email and a button to create a list.
I can also edit the name and email correctly, and it reflects in the firebase instantaneously. Ok. I get the user data and I can edit it.
What I'm trying to do now is to show the lists that the user has created.
Look, this user has created one list, and what is returned to me is that he doesn't have lists.
I'll try to shorten the code as much as possible:
<script>
imports.....
import { db } from '../../firebase.config.js'
let listings = []
let auth = getAuth()
// fetch the user's listings
const fetchUserListings = async () => {
const listingsRef = collection(db, 'listings')
const q = query(
listingsRef,
where('userRef', '==', auth.currentUser.uid),
orderBy('timestamp', 'desc')
)
const querySnap = await getDocs(q)
querySnap.forEach((doc) => {
return listings.push({
id: doc.id,
data: doc.data()
})
})
}
fetchUserListings()
</script>
<!-- display the user's listings -->
<div>
{#if listings.length > 0}
<p class="listingText">My lists</p>
{#each listings as listing}
<ListingItem listing={listing.data} id={listing.id} />
{/each}
{:else}
<p class="noListings">You have no lists</p>
{/if}
</div>
My ListItem component:
<script>
export let listing
export let id
export let handleDelete
import DeleteIcon from '../../static/assets/svg/deleteIcon.svg'
</script>
<li class="categoryListing">
<a href={`/category/${listing.type}/${id}`} class="categoryListingLink">
<img src={listing.imgUrls[0]} alt={listing.name} class="categoryListingImg" />
<div class="categoryListingDetails">
<p class="categoryListingLocation">
{listing.location}
</p>
<p class="CategoryListingName">
{listing.name}
</p>
<p class="categoryListingPrice">
${listing.offer ? listing.discountedPrice : listing.regularPrice}
{listing.type === 'rent' ? '/ por mês' : ''}
</p>
<div class="categoryListingInfoDiv">
<img src="/assets/svg/bedIcon.svg" alt="cama" />
<p class="categoryListingInfoText">
{listing.bedrooms > 1 ? `${listing.bedrooms} camas` : `${listing.bedrooms} cama`}
</p>
<img src="/assets/svg/bathtubIcon.svg" alt="banheiro" />
<p class="categoryListingInfoText">
{listing.bathrooms > 1
? `${listing.bathrooms} banheiros`
: `${listing.bathrooms} banheiro`}
</p>
</div>
</div>
</a>
{#if handleDelete}
<DeleteIcon
class="removeIcon"
fill="rgb(231, 76, 60)"
onClick={() => {
handleDelete(listing.id, listing.name)
}}
/>
{/if}
</li>
Just when you think you've reached the simplest part, it's still tough.
Update:
I think that the problem is in firebase. The "docs" are empty:
Now I am in serious trouble!
querySnap.forEach((doc) => {
return listings.push({
id: doc.id,
data: doc.data()
})
})
I see two things here. The less important: The .forEach() method returns undefined, so the return is redundant. The more important: the .push() alone won't automatically trigger updates. Have a look at this section in the Docs
Did you try logging listings? I assume the data is there, it's just not displayed, so I propose to change this part to
querySnap.forEach((doc) => {
listings = [...listings, {
id: doc.id,
data: doc.data()
}]
})
or
querySnap.forEach((doc) => {
listings.push({
id: doc.id,
data: doc.data()
})
listings = listings
})

vue js realtime chat app without refreshing / firebase

I'm creating a chat app,
If the user enter message and press send button, the app is working fine. The informations going to the database and im taking a datas.
When the user refresh the page, so there is no problem, in mounted() instance im taking the datas from database(firebase) and im showing on the app. If another user comes to the chat, also there is no problem, all messages are appearing.
The problem is that: If the new message is coming, another user can not see it without refresh or without send message button. When the another user send a message then the user see all messages.
I explain the problem with a gif, if you help me i will be glad.
<template>
<div>
<div class="container">
<div class="row">
<div v-if="isLogin" class="offset-3 col-md-6 msg-area">
<header>
<h1>Group Chat</h1>
<p class="sm">Welcome, {{this.username }} </p>
</header>
<div class="msg">
<p class="mssgs" v-for="(message,index) in messages" :key="index">{{ message.msg }} <br> <span> {{ message.name }} - {{ message.time }} </span> </p>
</div>
<div class="sendMsg">
<form #submit.prevent="sendFunc">
<div class="form-group d-flex">
<input type="text" class="form-control" placeholder="Enter message.." v-model="msgInput">
<button class="btn">Send </button>
</div>
</form>
</div>
</div>
<div class="offset-3 col-md-6 login" v-else>
<form #submit.prevent="joinFunc">
<div class="form-group d-flex">
<input type="text" class="form-control" placeholder="Enter username.." v-model="username">
<button class="btn">Join to Group </button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import firebase from "./firebase";
import 'firebase/firestore';
export default {
data() {
return {
db : firebase.firestore(),
isLogin: false,
username: '',
messages : [
],
msgInput: '',
}
},
methods: {
joinFunc() {
this.isLogin = true;
},
sendFunc() {
let date = new Date();
let hour = date.getHours();
let minute = date.getMinutes();
let nowTime = hour + ':' + minute;
this.db.collection("messages")
.add({ message: this.msgInput, name: this.username, time: nowTime, date: date })
.then(() => {
this.db.collection("messages").orderBy("date", "asc")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.messages.push({
name: doc.data().name,
msg: doc.data().message,
time: doc.data().time
});
});
})
})
.catch((error) => {
console.error("Error writing document: ", error);
});
},
},
mounted: function() {
this.db.collection("messages").orderBy("date", "asc")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
this.messages.push({
name: doc.data().name,
msg: doc.data().message,
time: doc.data().time
});
});
})
}
}
</script>
You're using get() to read the data from Firestore. As the documentation I linked explains, that reads the value from the database once, and does nothing more.
If you want to continue listening for updates to the database, you'll want to use a realtime listener. By using onSnapshot() your code will get called with a querySnapshot of the current state of the database right away, and will then also be called whenever the database changes. This is the perfect way to then update your UI.
So instead of
...
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
Do the following:
...
.onSnapshot((querySnapshot) => {
querySnapshot.forEach((doc) => {

How to use Vue.Draggble and sort Firebase Realtime Storage?

I have a Vue.js project and I have Customers List with First Name- Last Name- Address-City-State/Province-Email like this >>>
See project image here...
With Blue Button --Kayit Ol-- I fill the form and make a registiration. See picture>> Register Form
When I click the Yellow Button --Ayarlar-- I need to see the all cities in the Draggble List and when I sort in the Draggble and click the Green Button --Şehre Göre Sırala-- , the Customer List below should be sorted same as Draggble List. I used Vue.Draggble package (https://github.com/SortableJS/Vue.Draggable) but I cannot take firebase data properly and sort them.
SignUp.vue
// Musteriler
var clientRef = db.ref('musteriler/');
var newClient = clientRef.push();
//Cities
var citiesRef=db.ref('cities/');
var newCity = citiesRef.push();
export default {
data() {
return {
form: {
firstName: '',
lastName: '',
address: '',
city:'',
province:'',
email: '',
},
selectedCities:['Ankara', 'Adana',],
cities : ['','Adana', 'Adıyaman', 'Afyon', 'Ağrı', 'Amasya', 'Ankara', 'Antalya', 'Artvin',
'Aydın', 'Balıkesir', 'Bilecik', 'Bingöl', 'Bitlis', 'Bolu', 'Burdur', 'Bursa', 'Çanakkale',
'Çankırı', 'Çorum', 'Denizli', 'Diyarbakır', 'Edirne', 'Elazığ', 'Erzincan', 'Erzurum', 'Eskişehir',
'Gaziantep', 'Giresun', 'Gümüşhane', 'Hakkari', 'Hatay', 'Isparta', 'Mersin', 'İstanbul', 'İzmir',
'Kars', 'Kastamonu', 'Kayseri', 'Kırklareli', 'Kırşehir', 'Kocaeli', 'Konya', 'Kütahya', 'Malatya',
'Manisa', 'Kahramanmaraş', 'Mardin', 'Muğla', 'Muş', 'Nevşehir', 'Niğde', 'Ordu', 'Rize', 'Sakarya',
'Samsun', 'Siirt', 'Sinop', 'Sivas', 'Tekirdağ', 'Tokat', 'Trabzon', 'Tunceli', 'Şanlıurfa', 'Uşak',
'Van', 'Yozgat', 'Zonguldak', 'Aksaray', 'Bayburt', 'Karaman', 'Kırıkkale', 'Batman', 'Şırnak',
'Bartın', 'Ardahan', 'Iğdır', 'Yalova', 'Karabük', 'Kilis', 'Osmaniye', 'Düzce'],
show: true
}
},
methods: {
onSubmit(evt) {
evt.preventDefault();
newClient.set(this.form);
this.selectedCities.push(...this.selectedCities, this.form.city);
console.log(this.selectedCities);
newCity.set(this.selectedCities);
alert(JSON.stringify(this.form))
},
onReset(evt) {
evt.preventDefault()
// Reset our form values
this.form.firstName = ''
this.form.lastName = ''
this.form.address = ''
this.form.city = ''
this.form.province = ''
this.form.email = ''
// Trick to reset/clear native browser form validation state
this.show = false
this.$nextTick(() => {
this.show = true
})
}
}
}
</script>
Settings.Vue
<template>
<div class="col s12 drag">
<div class="col s12">
<p>Şehirleri sürükleyerek listeyi istediğiniz şekilde sıralandırabilirsiniz.</p>
</div>
<div class="col s12 mx-auto">
<draggable v-model="cities" class="draggable-container" #ended="onEnd" ghost-class='ghost'>
<transition-group type="transition" name="flip-list">
<div class="sortable mx-auto" v-for="item in cities" :key="item">
<strong>{{item}}</strong>
</div>
</transition-group>
</draggable>
<button class="btn btn-success mb-3" #click="onSort">Şehre Göre Sırala</button>
</div>
</div>
</template>
<script>
import {db} from '../firebase/db';
import draggable from 'vuedraggable'
var cityRef=db.ref('cities')
console.log(cityRef);
export default {
components: {
draggable,
},
firebase:{
cities: cityRef,
},
data() {
return {
}
},
methods: {
onEnd(evt) {
evt.preventDefault();
console.log(evt);
},
onSort() {
this.cities = this.cities.sort((a, b) => a.order - b.order);
}
}
}
</script>
Firebase Data
See Firebase RealTime Database musteriler Picture >>> musteriler.json
See Firebase RealTime Database cities Picture >>> cities.json
So how should I arrange the Database and how can I sort Customer List by Vue.Draggble properly ?
Thanks.

Update Firestore document ID and auth email with same method

I am teaching myself how to code by building a little project site but have been stuck for a few days trying to figure out how to update users' profile and account information. I am able to figure out how to retrieve and display the information but am having difficulty in getting them to update it
Goal:
Update the account email that they use at login
Update their full name stored in firestore (I figured this one out)
Update the document id of their user in Firestore (which is used for their profile slug)
So far I have been able to figure out how to display all three in the form fields, but only how to update the user's full name in Firestore with point 1 and 3 still escaping me.
For Goal 2 I used "Update a document" from Firestore documentation here https://firebase.google.com/docs/firestore/manage-data/add-data which works successfully
For updating user email I attempted to use the update email method from here https://firebase.google.com/docs/auth/web/manage-users
Screenshot of document in Firestore
<template>
<v-container fill-height>
<v-layout justify-center align-center v-if="profile">
<v-flex xs12 sm8 md8 style="max-width: 600px">
<v-card >
<v-toolbar dark color="primary">
<v-toolbar-title>Profile</v-toolbar-title>
</v-toolbar>
<v-flex class="ml-3 my-4">
<v-avatar size="75px" class="mr-2" >
<img
class="img-circle elevation-2 "
src="https://raw.githubusercontent.com/vuetifyjs/docs/dev/static/doc-images/lists/1.jpg"
>
</v-avatar>
<v-btn color="primary" >Upload</v-btn>
<v-btn>Delete</v-btn>
</v-flex>
<v-spacer></v-spacer>
<v-divider></v-divider>
<v-spacer></v-spacer>
<v-card-text>
<v-form>
<v-text-field
prepend-icon="person"
required
v-model="profile.fullname"
name="fullname"
label="Full Name"
type="text">
</v-text-field>
<v-text-field
v-if="user"
prepend-icon="email"
required
v-model="user.email"
name="email"
label="Email"
type="text">
</v-text-field>
<v-text-field
v-model="this.profile.id"
hint="Create a unique URL for your profile. Many people use their first and last name. <br> [Ex: reel.ly/misha.collins]"
persistent-hint
id="username"
prepend-icon="link"
name="username"
required
label="Profile URL "
type="text">
</v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<!-- <p class="red-text center" v-if="feedback">{{ feedback }}</p> -->
<v-btn flat #click.native="updatemyProfile" color="primary">Save</v-btn>
</v-card-actions>
</v-card>
<!-- <v-card style="margin-top: 30px" class="elevation-2">
<v-toolbar dark color="primary">
<v-toolbar-title>Billing</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<p class="red-text center" v-if="feedback">{{ feedback }}</p>
<v-btn flat #click.native="updateBilling" color="primary">Update Account</v-btn>
</v-card-actions>
</v-card> -->
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import db from '#/firebase/init'
import firebase from 'firebase'
import slugify from 'slugify'
export default {
name: 'Account',
data () {
return {
user: null,
profile: null,
feedback: null,
docid: null
}
},
created () {
let ref = db.collection('users')
// get current profile to list full name
ref.where('user_id', '==', firebase.auth().currentUser.uid).get()
.then(snapshot => {
snapshot.forEach(doc => {
this.profile = doc.data(),
this.profile.id = doc.id
})
})
// get current user to list email
firebase.auth().onAuthStateChanged((user) => {
if (user) {
this.user = user
// console.log(this.user)
} else {
this.user.uid = null
}
})
},
methods: {
updatemyProfile() {
// working to change fullname but not document id
let ref = db.collection('users')
// get current profile
ref.where('user_id', '==', firebase.auth().currentUser.uid).get()
.then(snapshot => {
snapshot.forEach(doc => {
this.profile = doc.data(),
this.profile.id = doc.id
})
})
var docRef = db.collection("users").doc(this.profile.id)
return docRef.update({
id: this.profile.id, // this is adding an id field and assigning this.profile.id value to it instead of updating the document id of the user
fullname: this.profile.fullname
})
// update email address
var user = firebase.auth().currentUser
user.updateEmail(this.user.email).then(function() {
console.log("success")
}).catch(function(error) {
// An error happened.
})
}
}
}
</script>
Any help is much appreciated!
Look at this bit of code at the end of your function:
var docRef = db.collection("users").doc(this.profile.id)
return docRef.update({
id: this.profile.id,
fullname: this.profile.fullname
})
// update email address
var user = firebase.auth().currentUser
user.updateEmail(this.user.email).then(function() {
console.log("success")
}).catch(function(error) {
// An error happened.
})
You're returning early from the function with the return keyword after you call the update() method, which means that any code after that in the function (the update of email address) won't get run.

Resources