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

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)
},
}

Related

How to use Firestore in Nuxt3 with SSR?

I am using Nuxt RC8 combined with Firestore.
My goal is to make the firestore request SSR and then combine it with Firestore's onSnapshot to get realtime updates after hydration is done.
I have created this composable useAssets:
import { computed, ref } from 'vue';
import { Asset, RandomAPI, RandomDatabase } from '#random/api';
/**
* Asset basic composable
* #param dbClient Database client
* #param options Extra options, like live data binding
*/
export function useAssets(dbClient: RandomDatabase) {
const assets = ref([]);
const unsubscribe = ref(null);
const searchQuery = ref('');
const randomAPI = RandomAPI.getInstance();
async function fetchAssets(options?: { live: boolean }): Promise<void> {
if (options?.live) {
try {
const query = randomAPI.fetchAssetsLive(dbClient, (_assets) => {
assets.value = _assets as Asset<any>[];
});
unsubscribe.value = query;
} catch (error) {
throw Error(`Error reading assets: ${error}`);
}
} else {
const query = await randomAPI.fetchAssetsStatic(dbClient);
assets.value = query;
}
}
const filteredAssets = computed(() => {
return searchQuery.value
? assets.value.filter((asset) =>
asset.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
: assets.value;
});
function reverseAssets(): void {
const newArray = [...assets.value];
assets.value = newArray.reverse();
}
return {
assets,
fetchAssets,
filteredAssets,
searchQuery,
reverseAssets,
unsubscribe,
};
}
The randomAPI.fetchAssetsLive comes from the firestore queries file:
export function fetchAssetsLive({
db,
callback,
options,
}: {
db: Firestore;
callback: (
assets: Asset<Timestamp>[] | QueryDocumentSnapshot<Asset<Timestamp>>[]
) => void;
options?: { fullDocs: boolean };
}): Unsubscribe {
const assetCollection = collection(db, 'assets') as CollectionReference<
Asset<Timestamp>
>;
if (options?.fullDocs) {
return onSnapshot(assetCollection, (querySnapshot) =>
callback(querySnapshot.docs)
);
}
// Return unsubscribe
return onSnapshot(assetCollection, (querySnapshot) =>
callback(querySnapshot.docs.map((doc) => doc.data()))
);
}
And then the component:
<template>
<div>
<h1>Welcome to Random!</h1>
<Button #click="reverseAssets">Reverse order</Button>
<ClientOnly>
<!-- <Input name="search" label="Search for an asset" v-model="searchQuery" /> -->
</ClientOnly>
<ul>
<li class="list-item" v-for="asset in assets" :key="asset.name">
Asset Name: {{ asset.name }} Type: {{ asset.type }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { Button, Input } from '#random/ui';
import { useNuxtApp, useAsyncData } from '#app';
const { $randomFirebase, $firestore, $getDocs, $collection } = useNuxtApp();
const { fetchAssets, filteredAssets, searchQuery, reverseAssets, assets } =
useAssets($randomFirebase);
// const a = process.client ? filteredAssets : assets;
onMounted(() => {
// console.log(searchQuery.value);
// fetchAssets({ live: true });
});
watch(
assets,
(val) => {
console.log('watcher: ', val);
},
{ deep: true, immediate: true }
);
// TODO: make SSR work
await useAsyncData(async () => {
await fetchAssets();
});
</script>
Why is it only loading via SSR and then assets.value goes []? Refreshing the page retrieves renders the items correctly but then once hydration comes in, it's gone.
Querying both, in onMounted and useAsyncData, makes it send correctly via SSR the values, makes it work client-side too but there is still a hydration missmatch, even being the values the same. And visually you only see the ones from the client-side request, not the SSR.
Is there a better approach? What am I not understanding?
I don't want to use firebase-admin as the SSR query maker because I want to use roles in the future (together with Firebase Auth via sessions).
I solved the hydration issue in two ways:
By displaying in the template only specific information, since JS objects are not ordered by default so there could be different order between the SSR query and the CS query.
By ordering by a field name in the query.
By making sure that the serverData is displayed until first load of the onsnapshot is there, so theres is not a mismatch this way: [data] -> [] -> [data]. For now I control it in the template in a very cheap way but it was for testing purposes:
<li class="list-item" v-for="asset in (isServer || (!isServer && !assets.length) ? serverData : assets)" :key="asset.name">
Asset Name: {{ asset.name }} Type: {{ asset.type }}
</li>
By using /server/api/assets.ts file with this:
import { getDocs, collection, query, orderBy, CollectionReference, Timestamp, Query } from 'firebase/firestore';
import { Asset } from '#random/api/dist';
import { firestore } from '../utils/firebase';
export default defineEventHandler(async (event) => {
const assetCollection = collection(firestore, 'assets');
let fullQuery: CollectionReference<Asset<Timestamp>> | Query<Asset<Timestamp>>;
try {
// #ts-ignore
fullQuery = query(assetCollection, orderBy('name'));
} catch (e) {
console.error(e)
// #ts-ignore
fullQuery = assetCollection;
}
const ref = await getDocs(fullQuery);
return ref.docs.map((doc) => doc.data());
});
And then in the component, executing:
const { data: assets } = useFetch('/api/assets');
onMounted(async () => {
fetchAssets({ live: true });
});
Still, if I try via useAsyncData it does not work correctly.

How to test computed value inside setup function in Vue.js 3 with vue-test-utils & Jest

I am getting "TypeError: Cannot add property myData, object is not extensible" on setData
Hello.vue
<template>
<div v-if="isEditable" id="myEditDiv">
<button type="button"> Edit </button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, reactive} from "vue"
export default defineComponent({
setup() {
const myObject = {myName:"", myNumber:""}
let myData = reactive({myObject})
const isEditable = computed(() => {
return myData.myObject.myName.startsWith('DNU') ? false : true
})
return {
isEditable
}
}
})
</script>
Hello.spec.ts
import { shallowMount } from '#vue/test-utils'
import Hello from '#/components/Hello.vue'
import { reactive } from 'vue'
describe('Hello.vue Test', () => {
it('is isEditable returns FALSE if NAME starts with DNU', async () => {
const myObject = {myName:"DNU Bad Name", myNumber:"12345"}
let myData = reactive({myObject})
const wrapper = shallowMount(Hello)
await wrapper.setData({'myData' : myData})
expect(wrapper.vm.isEditable).toBe(false)
})
})
I also tried to see if that DIV is visible by:
expect(wrapper.find('#myEditDiv').exists()).toBe(false)
still same error. I might be completely off the path, so any help would be appreciated.
Update
This is possible several different ways. There's two issues that need to be addressed.
The variable has to be made available. You can use vue's expose function in setup (but getting the value is really messy: wrapper.__app._container._vnode.component.subTree.component.exposed😱) or just include it in the return object (accessible through wrapper.vm).
change how you mutate the data in the test.
your test has
const myObject = {myName:"DNU Bad Name", myNumber:"12345"}
let myData = reactive({myObject})
const wrapper = shallowMount(Hello)
await wrapper.setData({'myData' : myData})
even if setData was able to override the internal, it would not work.
the problem is that the setup function has this
let myData = reactive({ myObject });
const isEditable = computed(() => {
return myData.myObject.myName.startsWith("DNU") ? false : true;
});
where editable is using a computed generated from that instance of myData. If you override myData with a separate reactive, the computed will still continue to use the old one. You need to replace the contents of the reactive and not the reactive itself
To update the entire content of the reactive, you can use:
Object.assign(myReactive, myNewData)
you can make that a method in your component, or just run that from the test. If you update any value within the reactive (like myData.myObject) you can skip the Object.asign
Here are several versions of how you can test it.
Component:
<template>
<div v-if="isEditable" id="myEditDiv">
<button type="button">Edit</button>
</div>
</template>
<script>
import { computed, defineComponent, reactive } from "vue";
export default defineComponent({
setup(_, { expose }) {
const myObject = { myName: "", myNumber: "" };
let myData = reactive({ myObject });
const isEditable = computed(() => {
return myData.myObject.myName.startsWith("DNU") ? false : true;
});
const updateMyData = (data) => Object.assign(myData, data);
expose({ updateMyData });
return {
isEditable,
updateMyData,
myData
};
},
});
</script>
the test
import { shallowMount } from "#vue/test-utils";
import MyComponent from "#/components/MyComponent.vue";
const data = { myObject: { myName: "DNU Bad Name" } };
describe("MyComponent.vue", () => {
it.only("sanity test", async () => {
const wrapper = shallowMount(MyComponent);
expect(wrapper.vm.isEditable).toBe(true);
});
it.only("myData", async () => {
const wrapper = shallowMount(MyComponent);
Object.assign(wrapper.vm.myData, data);
expect(wrapper.vm.isEditable).toBe(false);
});
it.only("myData", async () => {
const wrapper = shallowMount(MyComponent);
wrapper.vm.myData.myObject = data.myObject;
expect(wrapper.vm.isEditable).toBe(false);
});
it.only("updateMyData method via return", async () => {
const wrapper = shallowMount(MyComponent);
wrapper.vm.updateMyData(data);
expect(wrapper.vm.isEditable).toBe(false);
});
it.only("updateMyData method via expose🙄", async () => {
const wrapper = shallowMount(MyComponent);
wrapper.__app._container._vnode.component.subTree.component.exposed.updateMyData(
data
);
expect(wrapper.vm.isEditable).toBe(false);
});
});
It is not possible through setData
from the docs:
setData
Updates component internal data.
Signature:
setData(data: Record<string, any>): Promise<void>
Details:
setData does not allow setting new properties that are not defined in the component.
Also, notice that setData does not modify composition API setup() data.
It seems that updating internals with composition API is incompatible with setData. See the method name setData, refers to this.data and was likely kept in the vue test utils mostly for backwards compatibility.
I suspect the theory is that it's bad practice anyway to test, what would be considered, an implementation detail and the component test should focus on validating inputs an outputs only. Fundamentally though, this is a technical issue, because the setup function doesn't expose the refs and reactives created in the setup.
There is a MUCH easier way to do this.....
Put your composables in a separate file
Test the composables stand alone.
Here is the vue file:
<template>
<div>
<div>value: {{ counter }}</div>
<div>isEven: {{ isEven }}</div>
<button type="button" #click="increment">Increment</button>
</div>
</template>
<script setup lang='ts'>
import {sampleComposable} from "./sample.composable";
const {isEven, counter, increment} = sampleComposable();
</script>
Here is the composable:
import {computed, ref} from 'vue';
export function sampleComputed() {
const counter = ref(0);
function increment() {
counter.value++;
}
const isEven = computed(() => counter.value % 2 === 0);
return {counter, increment, isEven};
}
Here is the test:
import {sampleComposable} from "./sample.composable";
describe('sample', () => {
it('simple', () => {
const computed = sampleComposable();
expect(computed.counter.value).toEqual(0);
expect(computed.isEven.value).toEqual(true);
computed.increment();
expect(computed.counter.value).toEqual(1);
expect(computed.isEven.value).toEqual(false);
computed.increment();
expect(computed.counter.value).toEqual(2);
expect(computed.isEven.value).toEqual(true);
})
});
This just 'works'. You don't have to deal w/ mounting components or any other stuff, you are JUST TESTING JAVASCRIPT. It's faster and much cleaner. It seems silly to test the template anyway.
One way to make this easier to test is to put all of your dependencies as arguments to the function. For instance, pass in the props so it's easy to just put in dummy values as need. Same for emits.
You can tests watches as well. You just need to flush the promise after setting the value that is being watched:
composable.someWatchedThing.value = 6.5;
await flushPromises();
Here is my flushPromises (which I found here):
export function flushPromises() {
return new Promise(process.nextTick);
}

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>

When recaptcha gets rendered, a transparent container overlaps all the ui and spans all the viewport space

I'm working with next.js and firebase.
I'm doing authentication with google and with phone number.
The problem I have happens when authenticating with phone number, when the recaptcha is rendered, I can't click it and move forward to the next steps in the auth flow, because a transparent container also is rendered and overlaps completly ui.
I don't know why this happens, I followed every step of the google guide:
Phone authentication
My page component is the following...
In the page component I'm rendering a different form, one if it's time to write the verification code sent via SMS and another which is the first in being showed, to write and send the phone number.
import AppHead from '../../components/head'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from "react-i18next"
import Select from '../../components/select'
import Nav from '../../components/nav'
import PhoneNumberInput from '../../components/phone-input'
import Dialog from '../../components/dialog'
import Footer from '../../components/footer'
import { isPossiblePhoneNumber } from 'react-phone-number-input'
export default function Home({ M, authService, libService }) {
const verificationCodeInput = useRef(null)
const [phoneNumber, setPhoneNumber] = useState("")
const [shouldVerify, setShouldVerify] = useState(false)
const [openDialog, setOpenDialog] = useState(false)
const [valid, setValid] = useState(2)
const [confirmationCode, setConfirmationCode] = useState('')
const { t, i18n } = useTranslation('common')
let verificationValidClass = ""
const onChangePhoneNumber = (value) => {
setPhoneNumber(value)
};
const onClickLoginWithPhone = async (evt) => {
evt.preventDefault()
try {
if (isPossiblePhoneNumber(phoneNumber)) {
const confirmationResult = await authService.signInWithPhoneNumber(phoneNumber, window.recaptchaVerifier)
window.confirmationResult = confirmationResult
setShouldVerify(true)
} else {
setOpenDialog(true)
}
} catch (error) {
console.log(error)
}
};
const phoneNumberForm = (
<form className="home__formLogin">
<PhoneNumberInput onChangePhoneNumber={onChangePhoneNumber} phoneNumber={phoneNumber} labelText={t("forms.auth.phoneFieldLabel")} />
<button data-target="phoneLoginModal" className="btn-large home__login-phone modal-trigger" onClick={onClickLoginWithPhone}>
<span className="material-icons home__sendIcon" aria-hidden="true">
send
</span>
{t("forms.auth.sendPhone")}
</button>
<div id="recaptcha-container">
</div>
</form>
)
if (valid === 0) {
verificationValidClass = "invalid"
} else if (valid === 1) {
verificationValidClass = "valid"
}
useEffect(() => {
const elems = document.querySelectorAll('.modal');
const instances = M.Modal.init(elems);
window.recaptchaVerifier = authService.getVerifier('recaptcha-container', (response) => setShouldVerify(true), () => console.log("captcha-expired"))
}, []);
return (
<>
<main className="main">
<div className="main__layout home">
<Nav top={true} content={pickLanguage} contentLg={pickLanguageLG} />
<AppHead title={t("pages.login_phone.meta-title")} description={t("metaDescription")} />
<header className="home__header">
<h1>{shouldVerify ? t("pages.home.titleCode") : "LOGIN"}</h1>
<p>{t("pages.login_phone.subtitle_1")} <br /> {t("pages.login_phone.subtitle_2")}</p>
</header>
<section className="home__login-options">
<h1 className="home__formTitle">{shouldVerify ? t("pages.home.code") : t("pages.home.loginHeading")}</h1>
{shouldVerify ? verificationCodeForm : phoneNumberForm}
<Dialog open={openDialog} onConfirm={onClickConfirmDialog} heading={t("components.dialog.wrongPhoneNumber")} confirmText={t("components.dialog.wrongPhoneNumberConfirm")}>
{t("components.dialog.wrongPhoneNumberContent")}
</Dialog>
</section>
</div>
</main>
<Footer />
</>
)
}
In the useEffect callback I create the verifier object and then in onClickLoginWithPhone I submit the phone number.
The function call authService.signInWithPhoneNumber(phoneNumber, window.recaptchaVerifier) is implemented as shown below:
export default function AuthService(authProvider,firebase,firebaseApp) {
return Object.freeze({
signInWithPhoneNumber,
getVerifier,
getCredentials
})
function getVerifier(id,solvedCallback,expiredCallback){
return new firebase.auth.RecaptchaVerifier("recaptcha-container",{
'size': 'normal',
'callback': solvedCallback,
'expired-callback': expiredCallback
});
}
//used when user signs in with phone number and need to introduce a verification code sended via SMS
async function getCredentials(verificationId,verificationCode){
const credential = firebase.auth.PhoneAuthProvider.credential(verificationId,verificationCode);
const userCredential = await firebase.auth(firebaseApp).signInWithCredential(credential);
return userCredential
}
async function signInWithPhoneNumber(phoneNumber, phoneVerifier) {
const confirmationResult = await firebase.auth(firebaseApp).signInWithPhoneNumber(`${phoneNumber}`, phoneVerifier)
return confirmationResult
}
}
The container that is displayed is the following:
The part highlighted in blue is the full element which appears when I submit the phone number, and the part highlighted in red is the div which spans all the viewport and dont let me click the captcha widget
I really don't know why this thing is displayed, I'm not manipulating the DOM when submit the phone number, so I guess this problem is related to using next with firebase or with firebase itself.
Note: t("<json-object-field>"), t is just a function which reads an specific json file (a different one depending on the language the page is being showed) which contains all the strings translated to an specific language and this function returns a translated string individually.
The captcha widget is rendered but the container overlaps it, the container is transparent.

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