Rendering firestore values in Vue.js - firebase

I'm trying to build a simple app and am running into trouble getting Vue rendering data I'm trying to pull from Firestore. Below is the code for a vue page after someone logs in. Essentially I'm just trying to get it to display the name of the person who logged in. Currently I have each user's display name as the document ID for the get() function. Right now, it doesn't even seem like its running the 'firestore()' function since I tried outputting some text to the console at the beginning but I didn't see it. Any idea what's happening here? Thank you!
<template>
<v-container fluid>
<v-layout row wrap>
<v-flex xs12 class="text-xs-center" mt-5>
<h1>Home page</h1>
</v-flex>
<v-flex xs12 class="text-xs-center" mt-3>
<p>Welcome {{ name }}</p>
</v-flex>
</v-layout>
</v-container>
</template>
<script>
import firebase from 'firebase'
export default {
data () {
return {
name: ''
}
},
firestore () {
firebase.firestore().collection('user').doc(firebase.auth().currentUser.displayName).get()
.then(doc => {
if (doc) {
var data = doc.data()
return {
name: data.name
}
} else {
console.log('No document exists')
}
}).catch(error => { console.log('Error: ' + error) })
}
}
</script>

You can use created hook for this purpose.
created() {
firebase.firestore().collection('user').doc(firebase.auth().currentUser.displayName).get()
.then(doc => {
if (doc) {
var data = doc.data()
this.name = data.name
} else {
console.log('No document exists')
}
}).catch(error => { console.log('Error: ' + error) })
}
If you're using vuefire, you can do
firestore: {
users: firebase.firestore().collection('user').doc(firebase.auth().currentUser.displayName)
}
then use this.users in your Vue code

Related

Svelte - UI does not get rendered which data I fetch from API

This is my script-tag:
<script lang="ts">
import Card from "../../components/Card.svelte";
import { onMount } from "svelte";
let show_all = false;
let data: Array<{id: number, image_url: string, description: string, link: string, title: string}> = [];
onMount(async () => {
try {
console.log(data)
let response = await fetch("http://localhost:4000/entries");
data = await response.json();
} catch(err) {
console.log(err);
}
});
const getData = async () => {
console.log(data)
if (!show_all) {
return data.slice(0, 12);
} else {
return data;
}
</script>
I render the data like this:
{#await getData()}
<p>...waiting</p>
{:then array}
{#each array as item (item.id)}
<Card image_url={item.image_url} description={item.description} link={item.link} title={item.title} />
{/each}
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
Fetching the data from the API works fine, but it seems to getData() function works with the empty array, not with data after it has been updated.
What am I doing wrong here? I thought using the onMount hook would guarantee that the UI gets rendered after the data was fetched.
Can anyone help me to fix this
The order is also logged here:
getdata
hook
hook should be called before getData
The problem is, as you mentioned, the order. getData() runs before the onMount callback. I assume waiting... should be shown while the data is fetched and the displayed data should also react to changes of show_all?
Here's one way in case data isn't otherwise needed inside the script tag
<script lang="ts">
import Card from "../../components/Card.svelte";
let show_all = false;
const getData = async () => {
try {
let response = await fetch("http://localhost:4000/entries");
return await response.json();
} catch(err) {
console.log(err);
}
}
</script>
{#await getData()}
<p>...waiting</p>
{:then data}
{#const dataToBeShown = show_all ? data : data.slice(0, 12)}
{#each dataToBeShown as item (item.id)}
<Card {...item} />
{/each}
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
in case it is
<script lang="ts">
import Card from "../../components/Card.svelte";
let show_all = false;
let data: Array<{id: number, image_url: string, description: string, link: string, title: string}> = [];
$: dataToBeShown = show_all ? data : data.slice(0, 12)
const getData = async () => {
try {
let response = await fetch("http://localhost:4000/entries");
data = await response.json();
} catch(err) {
console.log(err);
}
}
</script>
{#await getData()}
<p>...waiting</p>
{:then _}
{#each dataToBeShown as item (item.id)}
<Card {...item} />
{/each}
{:catch error}
<p style="color: red">{error.message}</p>
{/await}
Since the property names seem to match >> <Card {...item} />

How to populate text-fields from pinia store state without changing the rendered values from other components?

hope you're well!
I have a Vue 3 app using Pinia + Vuetify 3. I've defined a "client" store and a component that, upon render, will call a store action that calls my backend API and sets my client state (JSON) with the result.
clientStore.js:
export const useClientStore = defineStore('clients', {
state: () => ({
//Loading state and client(s)
loading: false,
clients: [],
client: {}
}),
getters: {
//Get all clients
getClients(state) {
return state.clients
},
//Get one client
getClient(state) {
return state.client
}
},
actions: {
//Get one client
async fetchClient(clientId) {
try {
this.loading = true
const data = await axiosConfig.get('/clients/' + clientId)
this.client = data.data
this.loading = false
} catch (error) {
this.loading = false
console.log("Error fetching client: " + clientId)
},
//snipped
I have a computed property that returns the client from the store and render them as follows:
Component.vue:
<template>
<div class="text-center py-5">
<div class="text-h4 font-weight-bold">{{ client.name }}</div>
</div>
<div class="d-flex justify-space-between">
<div class="text-h5">Description</div>
<v-btn #click="dialog = true" prepend-icon="mdi-cog" color="primary">Edit</v-btn>
</div>
<v-textarea class="py-5" :value="client.description" readonly auto-grow outlined>{{ client.description
}}</v-textarea>
<updateClient v-model="dialog" />
</template>
<script setup>
import updateClient from '#/components/clients/updateClient.vue'
import { useClientStore } from '#/store/clients'
import { computed, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router'
const store = useClientStore()
const route = useRoute()
const dialog = ref(false)
const client = computed(() => {
return store.client
})
onMounted(() => {
store.fetchClient(route.params.clientId)
})
</script>
My aim is to make an "EDIT" component - a popup dialog - that takes the client state values and pre-populate them in my text fields and upon changing the values, submit and PATCH the client in the backend.
updateClient.vue
<template>
<v-dialog max-width="500">
<v-card class="pa-5">
<v-card-title>Edit client</v-card-title>
<v-text-field label="Name" v-model="client.name"></v-text-field>
<v-textarea label="Description" v-model="client.description"></v-textarea>
<v-btn block outlined color="primary" #click="updateClient">Update Client</v-btn>
</v-card>
</v-dialog>
</template>
<script setup>
import { useClientStore } from '#/store/clients'
import {computed} from 'vue'
const store = useClientStore()
const client = computed(() => {
return store.client
})
</script>
Problem is when I edit the pre-populated values in the fields, it changes the values outside the dialog as seen in the video and stay changed even after closing the pop-up. Ideally I'd like the values in my Component.vue to be static and have my state values unaltered. How can this be solved?
Thanks!
When you bind client.name to a text field in "Edit component", you directly change values stored in pinia. This, by design, changes values in your "View component".
A simple answer is... just create a copy of the object.
Now, I know, I know... there is a reason why you used computed properties in both places. Because you're waiting on the server to return the initial values.
The easiest way to solve this is to create a copy of the client object in pinia store. Then, just use copy of the object for text field binding in "Edit component".
state: () => ({
//Loading state and client(s)
loading: false,
clients: [],
client: {},
clientEdit: {} // Make changes to this object instead
})
In api response
actions: {
//Get one client
async fetchClient(clientId) {
try {
this.loading = true
const data = await axiosConfig.get('/clients/' + clientId)
this.client = data.data
this.clientEdit = { ...this.client } // Copy client object
this.loading = false
} catch (error) {
this.loading = false
console.log("Error fetching client: " + clientId)
},
}

How to populate FormKit input fields with dynamic data fetched from a database

I'm making a fullstack app with vue3, axios using FormKit. For editing existing records I want to populate the input fields with the current data fetched from a mysql database. I stripped down the code to everything needed to display my problem, which in this code example is populating the FormKit input field with the lotnumber I fetched via the asynchronous function "getLotById". The lotnumber appears in the paragraph section but not in the input field. How can I properly delay the rendering of the FormKit element until the lotnumber has been fetched? Here's my code:
<script>
// import axios
import axios from "axios";
export default {
name: "LotEdit",
data() {
return {
lotnumber: this.lotnumber
}
},
props: {
lotid: Number
},
created: async function () {
await this.getLotById();
},
methods: {
// Get Lot By Id
async getLotById() {
try {
const response = await axios.get(`http://localhost:5000/lot/${this.$route.params.id}`);
this.lotnumber = response.data.lotnumber;
console.log(response.data);
}
catch (err) {
console.log(err);
}
},
}
};
</script>
<template>
<div>
<FormKit
type="text"
name="lotnumber"
label="lotnumber"
placeholder=""
validation="required"
:value="lotnumber"
/>
</div>
<div>
<p> Here the lotnumber appears: {{ lotnumber }}</p>
</div>
</template>
I suggest using a v-model on the FormKit input. Because it is two-way bound it means as soon as the async/await completes the data is populated on the template too. Something like...
<FormKit
v-model="lotnumber"
type="text"
name="lotnumber"
label="lotnumber"
placeholder=""
validation="required"
:value="lotnumber"
/>
Getting a little smarter I managed to solve the problem in the following way:
<script>
// import axios
import axios from "axios";
export default {
name: "LotEdit",
data() {
return {
lotnumber: this.lotnumber
}
},
props: {
lotid: Number
},
mounted: async function () {
const response = await this.getLotById();
const node = this.$formkit.get('lotnumber')
node.input(response.data.lotnumber, false)
},
methods: {
// Get Lot By Id
async getLotById() {
try {
const response = await axios.get(`http://localhost:5000/lot/${this.$route.params.id}`);
console.log(response.data);
return response;
}
catch (err) {
console.log(err);
}
},
}
};
</script>
<template>
<div>
<FormKit
type="text"
id="lotnumber"
name="lotnumber"
label="lotnumber"
placeholder=""
validation="required"
:value="lotnumber"
/>{{ lotnumber }}
</div>
</template>
Feel free to post any recommendations as I'm not a pro yet...
I'm also still figuring out how to handle controlled forms but I guess an alternative way to do it is with Form Generation
<script>
export default {
// ...
async setup() {
try {
const response = await axios.get(`http://localhost:5000/lot/${this.$route.params.id}`);
const schema = [
{
$formkit: "text",
label: "Lot Number",
value: response.data.lotnumber,
validation: "required",
},
];
} catch (err) {
console.log(err);
}
return { schema }
}
// ...
}
</script>
<template>
<FormKit type="form">
<FormKitSchema :schema="schema" />
</FormKit>
</template>

Data not display inside vuetify v-text-field with Nuxt & Firestore

this is a related question which I've asked before:
Get document data from Firestore and show the data into each of the form input field using Nuxt & Vuetify
I want the data that I've submitted to display on the v-text-field input.
As you can see from the image, I can submit my form and I can get the data accordingly.
Name: siradley_ <-- this come from my firestore
but I want it to display inside the v-text-field input, not outside the input field.
Currently, I still not know how to do it.
Any help on how to do it?
<template>
<div>
<v-container>
<v-layout>
<v-flex>
<v-card>
<v-card-text>
<v-form>
<v-layout>
<v-row>
<v-col cols="12" sm="6" md="6" v-for="(test, id) in Test">
<v-text-field
v-model="editedItem.name">{{ test.name }}</v-text-field>
<p>Name: {{ test.name }}</p>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-btn #click="test">Test</v-btn>
</v-col>
</v-row>
</v-layout>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</div>
</template>
my script
<script>
import firebase from "firebase/app";
import firestore from "#/plugins/firebasetest";
export default {
middleware: "authentication",
layout: 'dashboard',
data: () => ({
Test: [
{
name: '',
}
],
editedItem: {
name: '',
}
}),
created() {
this.readTest();
},
methods: {
readTest() {
this.Test = [];
firestore
.collection('test')
.doc(firebase.auth().currentUser.uid)
.get()
.then((doc) => {
this.Test.push({ ...doc.data(), id: doc.id });
console.log({ ...doc.data(), id: doc.id });
})
},
test() {
var data = {
name: this.editedItem.name,
}
firestore
.collection('test')
.doc(firebase.auth().currentUser.uid)
.set(data)
.then((doc) => {
window.location.reload();
console.log({ ...doc.data, id: doc.id })
})
},
},
}
</script>
you have to update the value editedItem.name in your data object after getting the value from the server.
you have two way data binding set on the v-text-field (i.e. v-model), so you just need to use v-text-field like this:
<v-text-field v-model="editedItem.name" ></v-text-field>
and then in the code after you get the data from the server update the mentioned field in the data:
async getDataFromServerExample() {
const data = await getData();
this.editedItem.name = data.properFieldFromServerResponse;
}
then everything should work fine.
Finally can solve it hehe.
Referring to this Unable to display data in Vue.js form after pulling from Cloud Firestore , I now can display my input data inside my v-text-field.
Here's how I do it:
<v-form>
<v-layout>
<v-row :items="Test">
<v-col cols="12" sm="6" md="6">
<v-text-field
v-model="editedItem.name"></v-text-field>
</v-col>
<v-col cols="12" sm="6" md="6">
<v-btn #click="test">Test</v-btn>
</v-col>
</v-row>
</v-layout>
</v-form>
<script>
import firebase from "firebase/app";
import firestore from "#/plugins/firebasetest";
export default {
middleware: "authentication",
layout: 'dashboard',
data: () => ({
editedItem: {
name: '',
}
}),
created() {
this.readTest();
},
methods: {
readTest() {
let _this = this;
firestore
.collection('test')
.doc(firebase.auth().currentUser.uid)
.get()
.then((doc) => {
if (doc.exists) {
console.log("Document data:", doc.data())
_this.editedItem.name = doc.data().name
} else {
console.log("No Document!");
}
}).catch((error) => {
console.log("Error :(");
})
},
},
}
</script>
I added let _this = this; and _this.editedItem.name = doc.data().name and it works.

Vue js - reload component on database insert

I have the following setup
Component.vue (display db collections as grid in main page)
...
<v-flex v-for="i in items" :key="i.id" xs6 sm3 md3>
<v-card color="primary">
<v-card-text>
<h2
class="font-weight-regular mb-4"
>
{{ i.description }}
</h2>
</v-card-text>
</v-card>
</v-flex>
...
<script>
import { db } from '~/plugins/firebase.js'
export default {
data: () => ({
items: []
}),
props: ['reload'],
watch: {
reload: function (newVal, oldVal) {
this.items = items
alert('changed reload')
}
},
methods: {
firestore() {
db.collection('items')
.get()
.then(querySnapshot => {
const items = []
querySnapshot.forEach(function(doc) {
const item = doc.data()
item.id = doc.id
items.push(useritem)
})
this.items = items
})
.catch(function(error) {
alert('Error getting documents: ' + error)
})
}
}
}
</script>
index.vue (main page that has grid component and button to add new collection)
....
<v-layout mb-4>
<v-btn
#click="submit"
>
Add Item
</v-btn>
</v-layout>
<v-layout mb-4>
<component :reload="reload" />
</v-layout>
....
<script>
import { db } from '~/plugins/firebase.js'
import component from '~/components/Component.vue'
import moment from 'moment'
export default {
components: {
component
},
data() {
return {
description: 'test',
date: moment(),
reload: false
}
},
methods: {
submit() {
db.collection('items')
.add({
description: this.description,
deadline: new Date(moment(this.date)),
status: true
})
.then(docRef => {
this.reload = true
})
.catch(error => {
alert('Error adding document: ', error)
})
}
}
}
</script>
As can be seen, I've added a prop to the component to sort of trigger a reload of data from database whenever a new item is added on the main page using the button.
On successful insert the value changes from false to true. However the component grid does not reload. Refreshing the page shows the new item in grid.
How can i make the component reactive or trigger reload on addition of new item?
In your firestore method in Component.vue, you are using the get method which according to the firestore documentation, only retrieves the data once, it doesn't listen to any change, you'd have to refresh your page to see your updated changes.
However, to listen to changes to your firestore DB and update accordingly on your website, you have to set a listener, Cloud Firestore sends your listener an initial snapshot of the data, and then another snapshot each time the document changes.
methods: {
firestore() {
db.collection("items").onSnapshot(
snapshot => {
const documents = snapshot.docs.map(doc => {
const item = doc.data();
item.id = doc.id;
return item;
});
this.items = documents;
},
error => {
// handle errors
alert("Error getting documents: " + error);
}
);
}

Resources